refactor: http service (#2233)
This commit is contained in:
@@ -83,7 +83,7 @@ import {
|
||||
ItemStream,
|
||||
Platform,
|
||||
} from '@standardnotes/models'
|
||||
import { ClientDisplayableError } from '@standardnotes/responses'
|
||||
import { ClientDisplayableError, SessionListEntry } from '@standardnotes/responses'
|
||||
|
||||
import { SnjsVersion } from './../Version'
|
||||
import { SNLog } from '../Log'
|
||||
@@ -133,7 +133,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
/**
|
||||
* @deprecated will be fully replaced by @standardnotes/api::HttpService
|
||||
*/
|
||||
private deprecatedHttpService!: InternalServices.SNHttpService
|
||||
private deprecatedHttpService!: InternalServices.DeprecatedHttpService
|
||||
private declare httpService: HttpServiceInterface
|
||||
private payloadManager!: InternalServices.PayloadManager
|
||||
public protocolService!: EncryptionService
|
||||
@@ -599,13 +599,13 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
return this.syncService.isDatabaseLoaded()
|
||||
}
|
||||
|
||||
public getSessions(): Promise<
|
||||
(Responses.HttpResponse & { data: InternalServices.RemoteSession[] }) | Responses.HttpResponse
|
||||
> {
|
||||
public getSessions(): Promise<Responses.HttpResponse<SessionListEntry[]>> {
|
||||
return this.sessionManager.getSessionsList()
|
||||
}
|
||||
|
||||
public async revokeSession(sessionId: UuidString): Promise<Responses.HttpResponse | undefined> {
|
||||
public async revokeSession(
|
||||
sessionId: UuidString,
|
||||
): Promise<Responses.HttpResponse<Responses.SessionListResponse> | undefined> {
|
||||
if (await this.protectionService.authorizeSessionRevoking()) {
|
||||
return this.sessionManager.revokeSession(sessionId)
|
||||
}
|
||||
@@ -627,7 +627,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
return Common.compareVersions(userVersion, Common.ProtocolVersion.V004) >= 0
|
||||
}
|
||||
|
||||
public async getUserSubscription(): Promise<Subscription | Responses.ClientDisplayableError> {
|
||||
public async getUserSubscription(): Promise<Subscription | Responses.ClientDisplayableError | undefined> {
|
||||
return this.sessionManager.getSubscription()
|
||||
}
|
||||
|
||||
@@ -897,6 +897,9 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
service.deinit()
|
||||
}
|
||||
|
||||
this.httpService.deinit()
|
||||
;(this.httpService as unknown) = undefined
|
||||
|
||||
this.options.crypto.deinit()
|
||||
;(this.options as unknown) = undefined
|
||||
|
||||
@@ -939,7 +942,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
ephemeral = false,
|
||||
mergeLocal = true,
|
||||
awaitSync = false,
|
||||
): Promise<Responses.HttpResponse | Responses.SignInResponse> {
|
||||
): Promise<Responses.HttpResponse<Responses.SignInResponse>> {
|
||||
return this.userService.signIn(email, password, strict, ephemeral, mergeLocal, awaitSync)
|
||||
}
|
||||
|
||||
@@ -1161,8 +1164,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
this.diskStorageService.provideEncryptionProvider(this.protocolService)
|
||||
this.createChallengeService()
|
||||
this.createLegacyHttpManager()
|
||||
this.createApiService()
|
||||
this.createHttpService()
|
||||
this.createHttpServiceAndApiService()
|
||||
this.createUserServer()
|
||||
this.createUserRequestServer()
|
||||
this.createUserApiService()
|
||||
@@ -1419,20 +1421,6 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
this.services.push(this.userService)
|
||||
}
|
||||
|
||||
private createApiService() {
|
||||
this.apiService = new InternalServices.SNApiService(
|
||||
this.deprecatedHttpService,
|
||||
this.diskStorageService,
|
||||
this.options.defaultHost,
|
||||
this.inMemoryStore,
|
||||
this.options.crypto,
|
||||
this.sessionStorageMapper,
|
||||
this.legacySessionStorageMapper,
|
||||
this.internalEventBus,
|
||||
)
|
||||
this.services.push(this.apiService)
|
||||
}
|
||||
|
||||
private createUserApiService() {
|
||||
this.userApiService = new UserApiService(this.userServer, this.userRequestServer)
|
||||
}
|
||||
@@ -1486,7 +1474,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
}
|
||||
|
||||
private createLegacyHttpManager() {
|
||||
this.deprecatedHttpService = new InternalServices.SNHttpService(
|
||||
this.deprecatedHttpService = new InternalServices.DeprecatedHttpService(
|
||||
this.environment,
|
||||
this.options.appVersion,
|
||||
this.internalEventBus,
|
||||
@@ -1494,11 +1482,22 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
this.services.push(this.deprecatedHttpService)
|
||||
}
|
||||
|
||||
private createHttpService() {
|
||||
this.httpService = new HttpService(
|
||||
this.environment,
|
||||
this.options.appVersion,
|
||||
SnjsVersion,
|
||||
private createHttpServiceAndApiService() {
|
||||
this.httpService = new HttpService(this.environment, this.options.appVersion, SnjsVersion)
|
||||
|
||||
this.apiService = new InternalServices.SNApiService(
|
||||
this.httpService,
|
||||
this.diskStorageService,
|
||||
this.options.defaultHost,
|
||||
this.inMemoryStore,
|
||||
this.options.crypto,
|
||||
this.sessionStorageMapper,
|
||||
this.legacySessionStorageMapper,
|
||||
this.internalEventBus,
|
||||
)
|
||||
this.services.push(this.apiService)
|
||||
|
||||
this.httpService.setCallbacks(
|
||||
this.apiService.processMetaObject.bind(this.apiService),
|
||||
this.apiService.setSession.bind(this.apiService),
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@ import { removeFromArray } from '@standardnotes/utils'
|
||||
import { SNRootKey } from '@standardnotes/encryption'
|
||||
import { ChallengeService } from '../Challenge'
|
||||
import { ListedService } from '../Listed/ListedService'
|
||||
import { ActionResponse, HttpResponse } from '@standardnotes/responses'
|
||||
import { ActionResponse, DeprecatedHttpResponse } from '@standardnotes/responses'
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { ItemManager } from '@Lib/Services/Items/ItemManager'
|
||||
import {
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
} from '@standardnotes/models'
|
||||
import { SNSyncService } from '../Sync/SyncService'
|
||||
import { PayloadManager } from '../Payloads/PayloadManager'
|
||||
import { SNHttpService } from '../Api/HttpService'
|
||||
import { DeprecatedHttpService } from '../Api/DeprecatedHttpService'
|
||||
import {
|
||||
AbstractService,
|
||||
DeviceInterface,
|
||||
@@ -61,7 +61,7 @@ export class SNActionsService extends AbstractService {
|
||||
private itemManager: ItemManager,
|
||||
private alertService: AlertService,
|
||||
public deviceInterface: DeviceInterface,
|
||||
private httpService: SNHttpService,
|
||||
private httpService: DeprecatedHttpService,
|
||||
private payloadManager: PayloadManager,
|
||||
private protocolService: EncryptionService,
|
||||
private syncService: SNSyncService,
|
||||
@@ -185,7 +185,7 @@ export class SNActionsService extends AbstractService {
|
||||
message: 'An issue occurred while processing this action. Please try again.',
|
||||
}
|
||||
void this.alertService.alert(error.message)
|
||||
return { error } as HttpResponse
|
||||
return { error } as DeprecatedHttpResponse
|
||||
})
|
||||
|
||||
return response as ActionResponse
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { FeatureDescription } from '@standardnotes/features'
|
||||
import { isNullOrUndefined, joinPaths } from '@standardnotes/utils'
|
||||
import { joinPaths } from '@standardnotes/utils'
|
||||
import { SettingName, SubscriptionSettingName } from '@standardnotes/settings'
|
||||
import { ErrorTag } from '@standardnotes/common'
|
||||
import {
|
||||
AbstractService,
|
||||
ApiServiceInterface,
|
||||
@@ -34,14 +33,53 @@ import {
|
||||
} from '@standardnotes/services'
|
||||
import { FilesApiInterface } from '@standardnotes/files'
|
||||
import { ServerSyncPushContextualPayload, SNFeatureRepo, FileContent } from '@standardnotes/models'
|
||||
import * as Responses from '@standardnotes/responses'
|
||||
import {
|
||||
User,
|
||||
HttpStatusCode,
|
||||
KeyParamsResponse,
|
||||
SignInResponse,
|
||||
SignOutResponse,
|
||||
ChangeCredentialsResponse,
|
||||
RawSyncResponse,
|
||||
SessionRenewalResponse,
|
||||
SessionListResponse,
|
||||
UserFeaturesResponse,
|
||||
ListSettingsResponse,
|
||||
UpdateSettingResponse,
|
||||
GetSettingResponse,
|
||||
DeleteSettingResponse,
|
||||
GetSubscriptionResponse,
|
||||
GetAvailableSubscriptionsResponse,
|
||||
PostSubscriptionTokensResponse,
|
||||
GetOfflineFeaturesResponse,
|
||||
ListedRegistrationResponse,
|
||||
CreateValetTokenResponse,
|
||||
StartUploadSessionResponse,
|
||||
UploadFileChunkResponse,
|
||||
CloseUploadSessionResponse,
|
||||
DownloadFileChunkResponse,
|
||||
IntegrityPayload,
|
||||
CheckIntegrityResponse,
|
||||
GetSingleItemResponse,
|
||||
HttpResponse,
|
||||
HttpResponseMeta,
|
||||
ErrorTag,
|
||||
HttpRequestParams,
|
||||
HttpRequest,
|
||||
HttpVerb,
|
||||
ApiEndpointParam,
|
||||
ClientDisplayableError,
|
||||
CreateValetTokenPayload,
|
||||
HttpErrorResponse,
|
||||
HttpSuccessResponse,
|
||||
isErrorResponse,
|
||||
} from '@standardnotes/responses'
|
||||
import { LegacySession, MapperInterface, Session, SessionToken } from '@standardnotes/domain-core'
|
||||
import { HttpResponseMeta } from '@standardnotes/api'
|
||||
import { HttpServiceInterface } from '@standardnotes/api'
|
||||
import { SNRootKeyParams } from '@standardnotes/encryption'
|
||||
import { ApiEndpointParam, ClientDisplayableError, CreateValetTokenPayload } from '@standardnotes/responses'
|
||||
|
||||
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
||||
|
||||
import { HttpParams, HttpRequest, HttpVerb, SNHttpService } from './HttpService'
|
||||
import { isUrlFirstParty, TRUSTED_FEATURE_HOSTS } from '@Lib/Hosts'
|
||||
import { Paths } from './Paths'
|
||||
import { DiskStorageService } from '../Storage/DiskStorageService'
|
||||
@@ -65,7 +103,7 @@ export class SNApiService
|
||||
SettingsServerInterface
|
||||
{
|
||||
private session: Session | LegacySession | null
|
||||
public user?: Responses.User
|
||||
public user?: User
|
||||
private registering = false
|
||||
private authenticating = false
|
||||
private changing = false
|
||||
@@ -74,7 +112,7 @@ export class SNApiService
|
||||
private filesHost?: string
|
||||
|
||||
constructor(
|
||||
private httpService: SNHttpService,
|
||||
private httpService: HttpServiceInterface,
|
||||
private storageService: DiskStorageService,
|
||||
private host: string,
|
||||
private inMemoryStore: KeyValueStoreInterface<string>,
|
||||
@@ -96,7 +134,7 @@ export class SNApiService
|
||||
super.deinit()
|
||||
}
|
||||
|
||||
public setUser(user?: Responses.User): void {
|
||||
public setUser(user?: User): void {
|
||||
this.user = user
|
||||
}
|
||||
|
||||
@@ -161,25 +199,22 @@ export class SNApiService
|
||||
return V0_API_VERSION
|
||||
}
|
||||
|
||||
private params(inParams: Record<string | number | symbol, unknown>): HttpParams {
|
||||
private params(inParams: Record<string | number | symbol, unknown>): HttpRequestParams {
|
||||
const params = merge(inParams, {
|
||||
[ApiEndpointParam.ApiVersion]: this.apiVersion,
|
||||
})
|
||||
return params
|
||||
}
|
||||
|
||||
public createErrorResponse(message: string, status?: Responses.StatusCode): Responses.HttpResponse {
|
||||
return { error: { message, status } } as Responses.HttpResponse
|
||||
public createErrorResponse(message: string, status?: HttpStatusCode, tag?: ErrorTag): HttpErrorResponse {
|
||||
return { data: { error: { message, tag } }, status: status ?? HttpStatusCode.BadRequest }
|
||||
}
|
||||
|
||||
private errorResponseWithFallbackMessage(response: Responses.HttpResponse, message: string) {
|
||||
if (!response.error?.message) {
|
||||
response.error = {
|
||||
...response.error,
|
||||
status: response.error?.status ?? Responses.StatusCode.UnknownError,
|
||||
message,
|
||||
}
|
||||
private errorResponseWithFallbackMessage(response: HttpErrorResponse, message: string): HttpErrorResponse {
|
||||
if (!response.data.error.message) {
|
||||
response.data.error.message = message
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
@@ -196,44 +231,44 @@ export class SNApiService
|
||||
}
|
||||
}
|
||||
|
||||
private processResponse(response: Responses.HttpResponse) {
|
||||
private processSuccessResponseForMetaBody<T>(response: HttpSuccessResponse<T>) {
|
||||
if (response.meta) {
|
||||
this.processMetaObject(response.meta)
|
||||
}
|
||||
}
|
||||
|
||||
private async request(params: {
|
||||
private async request<T>(params: {
|
||||
verb: HttpVerb
|
||||
url: string
|
||||
fallbackErrorMessage: string
|
||||
params?: HttpParams
|
||||
params?: HttpRequestParams
|
||||
rawBytes?: Uint8Array
|
||||
authentication?: string
|
||||
customHeaders?: Record<string, string>[]
|
||||
responseType?: XMLHttpRequestResponseType
|
||||
external?: boolean
|
||||
}) {
|
||||
}): Promise<HttpResponse<T>> {
|
||||
try {
|
||||
const response = await this.httpService.runHttp(params)
|
||||
this.processResponse(response)
|
||||
return response
|
||||
const response = await this.httpService.runHttp<T>(params)
|
||||
if (isErrorResponse(response)) {
|
||||
return this.errorResponseWithFallbackMessage(response, params.fallbackErrorMessage)
|
||||
} else {
|
||||
this.processSuccessResponseForMetaBody(response)
|
||||
return response
|
||||
}
|
||||
} catch (errorResponse) {
|
||||
return this.errorResponseWithFallbackMessage(errorResponse as Responses.HttpResponse, params.fallbackErrorMessage)
|
||||
return this.errorResponseWithFallbackMessage(errorResponse as HttpErrorResponse, params.fallbackErrorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mfaKeyPath The params path the server expects for authentication against
|
||||
* a particular mfa challenge. A value of foo would mean the server
|
||||
* would receive parameters as params['foo'] with value equal to mfaCode.
|
||||
* @param mfaCode The mfa challenge response value.
|
||||
*/
|
||||
async getAccountKeyParams(dto: {
|
||||
email: string
|
||||
mfaKeyPath?: string
|
||||
mfaCode?: string
|
||||
authenticatorResponse?: Record<string, unknown>
|
||||
}): Promise<Responses.KeyParamsResponse | Responses.HttpResponse> {
|
||||
}): Promise<HttpResponse<KeyParamsResponse>> {
|
||||
const codeVerifier = this.crypto.generateRandomKey(256)
|
||||
this.inMemoryStore.setValue(StorageKey.CodeVerifier, codeVerifier)
|
||||
|
||||
@@ -242,10 +277,10 @@ export class SNApiService
|
||||
const params = this.params({
|
||||
email: dto.email,
|
||||
code_challenge: codeChallenge,
|
||||
})
|
||||
}) as Record<string, unknown>
|
||||
|
||||
if (dto.mfaKeyPath !== undefined && dto.mfaCode !== undefined) {
|
||||
params[dto.mfaKeyPath] = dto.mfaCode
|
||||
if (dto.mfaCode !== undefined) {
|
||||
params['mfa_code'] = dto.mfaCode
|
||||
}
|
||||
|
||||
if (dto.authenticatorResponse) {
|
||||
@@ -266,9 +301,9 @@ export class SNApiService
|
||||
email: string
|
||||
serverPassword: string
|
||||
ephemeral: boolean
|
||||
}): Promise<Responses.SignInResponse | Responses.HttpResponse> {
|
||||
}): Promise<HttpResponse<SignInResponse>> {
|
||||
if (this.authenticating) {
|
||||
return this.createErrorResponse(API_MESSAGE_LOGIN_IN_PROGRESS) as Responses.SignInResponse
|
||||
return this.createErrorResponse(API_MESSAGE_LOGIN_IN_PROGRESS, HttpStatusCode.BadRequest)
|
||||
}
|
||||
this.authenticating = true
|
||||
const url = joinPaths(this.host, Paths.v2.signIn)
|
||||
@@ -279,7 +314,7 @@ export class SNApiService
|
||||
code_verifier: this.inMemoryStore.getValue(StorageKey.CodeVerifier) as string,
|
||||
})
|
||||
|
||||
const response = await this.request({
|
||||
const response = await this.request<SignInResponse>({
|
||||
verb: HttpVerb.Post,
|
||||
url,
|
||||
params,
|
||||
@@ -293,11 +328,8 @@ export class SNApiService
|
||||
return response
|
||||
}
|
||||
|
||||
signOut(): Promise<Responses.SignOutResponse> {
|
||||
const url = joinPaths(this.host, Paths.v1.signOut)
|
||||
return this.httpService.postAbsolute(url, undefined, this.getSessionAccessToken()).catch((errorResponse) => {
|
||||
return errorResponse
|
||||
}) as Promise<Responses.SignOutResponse>
|
||||
signOut(): Promise<HttpResponse<SignOutResponse>> {
|
||||
return this.httpService.post<SignOutResponse>(Paths.v1.signOut, undefined, this.getSessionAccessToken())
|
||||
}
|
||||
|
||||
async changeCredentials(parameters: {
|
||||
@@ -306,38 +338,33 @@ export class SNApiService
|
||||
newServerPassword: string
|
||||
newKeyParams: SNRootKeyParams
|
||||
newEmail?: string
|
||||
}): Promise<Responses.ChangeCredentialsResponse | Responses.HttpResponse> {
|
||||
}): Promise<HttpResponse<ChangeCredentialsResponse>> {
|
||||
if (this.changing) {
|
||||
return this.createErrorResponse(API_MESSAGE_CHANGE_CREDENTIALS_IN_PROGRESS)
|
||||
return this.createErrorResponse(API_MESSAGE_CHANGE_CREDENTIALS_IN_PROGRESS, HttpStatusCode.BadRequest)
|
||||
}
|
||||
const preprocessingError = this.preprocessingError()
|
||||
if (preprocessingError) {
|
||||
return preprocessingError
|
||||
}
|
||||
this.changing = true
|
||||
const url = joinPaths(this.host, Paths.v1.changeCredentials(parameters.userUuid) as string)
|
||||
const path = Paths.v1.changeCredentials(parameters.userUuid)
|
||||
const params = this.params({
|
||||
current_password: parameters.currentServerPassword,
|
||||
new_password: parameters.newServerPassword,
|
||||
new_email: parameters.newEmail,
|
||||
...parameters.newKeyParams.getPortableValue(),
|
||||
})
|
||||
const response = await this.httpService
|
||||
.putAbsolute(url, params, this.getSessionAccessToken())
|
||||
.catch(async (errorResponse) => {
|
||||
if (Responses.isErrorResponseExpiredToken(errorResponse)) {
|
||||
return this.refreshSessionThenRetryRequest({
|
||||
verb: HttpVerb.Put,
|
||||
url,
|
||||
params,
|
||||
})
|
||||
}
|
||||
return this.errorResponseWithFallbackMessage(errorResponse, API_MESSAGE_GENERIC_CHANGE_CREDENTIALS_FAIL)
|
||||
})
|
||||
|
||||
this.processResponse(response)
|
||||
const response = await this.httpService.put<ChangeCredentialsResponse>(path, params, this.getSessionAccessToken())
|
||||
|
||||
this.changing = false
|
||||
|
||||
if (isErrorResponse(response)) {
|
||||
return this.errorResponseWithFallbackMessage(response, API_MESSAGE_GENERIC_CHANGE_CREDENTIALS_FAIL)
|
||||
}
|
||||
|
||||
this.processSuccessResponseForMetaBody(response)
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
@@ -346,79 +373,54 @@ export class SNApiService
|
||||
lastSyncToken: string,
|
||||
paginationToken: string,
|
||||
limit: number,
|
||||
): Promise<Responses.RawSyncResponse | Responses.HttpResponse> {
|
||||
): Promise<HttpResponse<RawSyncResponse>> {
|
||||
const preprocessingError = this.preprocessingError()
|
||||
if (preprocessingError) {
|
||||
return preprocessingError
|
||||
}
|
||||
const url = joinPaths(this.host, Paths.v1.sync)
|
||||
const path = Paths.v1.sync
|
||||
const params = this.params({
|
||||
[ApiEndpointParam.SyncPayloads]: payloads,
|
||||
[ApiEndpointParam.LastSyncToken]: lastSyncToken,
|
||||
[ApiEndpointParam.PaginationToken]: paginationToken,
|
||||
[ApiEndpointParam.SyncDlLimit]: limit,
|
||||
})
|
||||
const response = await this.httpService
|
||||
.postAbsolute(url, params, this.getSessionAccessToken())
|
||||
.catch<Responses.HttpResponse>(async (errorResponse) => {
|
||||
this.preprocessAuthenticatedErrorResponse(errorResponse)
|
||||
if (Responses.isErrorResponseExpiredToken(errorResponse)) {
|
||||
return this.refreshSessionThenRetryRequest({
|
||||
verb: HttpVerb.Post,
|
||||
url,
|
||||
params,
|
||||
})
|
||||
}
|
||||
return this.errorResponseWithFallbackMessage(errorResponse, API_MESSAGE_GENERIC_SYNC_FAIL)
|
||||
})
|
||||
this.processResponse(response)
|
||||
const response = await this.httpService.post<RawSyncResponse>(path, params, this.getSessionAccessToken())
|
||||
|
||||
if (isErrorResponse(response)) {
|
||||
this.preprocessAuthenticatedErrorResponse(response)
|
||||
return this.errorResponseWithFallbackMessage(response, API_MESSAGE_GENERIC_SYNC_FAIL)
|
||||
}
|
||||
|
||||
this.processSuccessResponseForMetaBody(response)
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
private async refreshSessionThenRetryRequest(httpRequest: HttpRequest): Promise<Responses.HttpResponse> {
|
||||
const sessionResponse = await this.refreshSession()
|
||||
if (sessionResponse.error || isNullOrUndefined(sessionResponse.data)) {
|
||||
return sessionResponse
|
||||
} else {
|
||||
return this.httpService
|
||||
.runHttp({
|
||||
...httpRequest,
|
||||
authentication: this.getSessionAccessToken(),
|
||||
})
|
||||
.catch((errorResponse) => {
|
||||
return errorResponse
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async refreshSession(): Promise<Responses.SessionRenewalResponse | Responses.HttpResponse> {
|
||||
async refreshSession(): Promise<HttpResponse<SessionRenewalResponse>> {
|
||||
const preprocessingError = this.preprocessingError()
|
||||
if (preprocessingError) {
|
||||
return preprocessingError
|
||||
}
|
||||
|
||||
this.refreshingSession = true
|
||||
const url = joinPaths(this.host, Paths.v1.refreshSession)
|
||||
|
||||
const session = this.session as Session
|
||||
const params = this.params({
|
||||
access_token: session.accessToken.value,
|
||||
refresh_token: session.refreshToken.value,
|
||||
})
|
||||
const result = await this.httpService
|
||||
.postAbsolute(url, params)
|
||||
|
||||
const response = await this.httpService
|
||||
.post<SessionRenewalResponse>(Paths.v1.refreshSession, params)
|
||||
.then(async (response) => {
|
||||
const sessionRenewalResponse = response as Responses.SessionRenewalResponse
|
||||
if (
|
||||
sessionRenewalResponse.error ||
|
||||
sessionRenewalResponse.data?.error ||
|
||||
!sessionRenewalResponse.data.session
|
||||
) {
|
||||
return null
|
||||
if (isErrorResponse(response) || !response.data.session) {
|
||||
return response
|
||||
}
|
||||
|
||||
const accessTokenOrError = SessionToken.create(
|
||||
sessionRenewalResponse.data.session.access_token,
|
||||
sessionRenewalResponse.data.session.access_expiration,
|
||||
response.data.session.access_token,
|
||||
response.data.session.access_expiration,
|
||||
)
|
||||
if (accessTokenOrError.isFailed()) {
|
||||
return null
|
||||
@@ -426,19 +428,15 @@ export class SNApiService
|
||||
const accessToken = accessTokenOrError.getValue()
|
||||
|
||||
const refreshTokenOrError = SessionToken.create(
|
||||
sessionRenewalResponse.data.session.refresh_token,
|
||||
sessionRenewalResponse.data.session.refresh_expiration,
|
||||
response.data.session.refresh_token,
|
||||
response.data.session.refresh_expiration,
|
||||
)
|
||||
if (refreshTokenOrError.isFailed()) {
|
||||
return null
|
||||
}
|
||||
const refreshToken = refreshTokenOrError.getValue()
|
||||
|
||||
const sessionOrError = Session.create(
|
||||
accessToken,
|
||||
refreshToken,
|
||||
sessionRenewalResponse.data.session.readonly_access,
|
||||
)
|
||||
const sessionOrError = Session.create(accessToken, refreshToken, response.data.session.readonly_access)
|
||||
if (sessionOrError.isFailed()) {
|
||||
return null
|
||||
}
|
||||
@@ -447,7 +445,7 @@ export class SNApiService
|
||||
this.session = session
|
||||
|
||||
this.setSession(session)
|
||||
this.processResponse(response)
|
||||
this.processSuccessResponseForMetaBody(response)
|
||||
|
||||
await this.notifyEventSync(ApiServiceEvent.SessionRefreshed, {
|
||||
session,
|
||||
@@ -455,105 +453,94 @@ export class SNApiService
|
||||
|
||||
return response
|
||||
})
|
||||
.catch((errorResponse) => {
|
||||
this.preprocessAuthenticatedErrorResponse(errorResponse)
|
||||
return this.errorResponseWithFallbackMessage(errorResponse, API_MESSAGE_GENERIC_TOKEN_REFRESH_FAIL)
|
||||
})
|
||||
|
||||
this.refreshingSession = false
|
||||
|
||||
if (result === null) {
|
||||
return this.createErrorResponse(API_MESSAGE_INVALID_SESSION)
|
||||
if (response === null) {
|
||||
return this.createErrorResponse(API_MESSAGE_INVALID_SESSION, HttpStatusCode.BadRequest)
|
||||
}
|
||||
|
||||
return result
|
||||
if (isErrorResponse(response)) {
|
||||
this.preprocessAuthenticatedErrorResponse(response)
|
||||
return this.errorResponseWithFallbackMessage(response, API_MESSAGE_GENERIC_TOKEN_REFRESH_FAIL)
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
async getSessionsList(): Promise<Responses.SessionListResponse | Responses.HttpResponse> {
|
||||
async getSessionsList(): Promise<HttpResponse<SessionListResponse>> {
|
||||
const preprocessingError = this.preprocessingError()
|
||||
if (preprocessingError) {
|
||||
return preprocessingError
|
||||
}
|
||||
const url = joinPaths(this.host, Paths.v1.sessions)
|
||||
const response = await this.httpService
|
||||
.getAbsolute(url, {}, this.getSessionAccessToken())
|
||||
.catch(async (errorResponse) => {
|
||||
this.preprocessAuthenticatedErrorResponse(errorResponse)
|
||||
if (Responses.isErrorResponseExpiredToken(errorResponse)) {
|
||||
return this.refreshSessionThenRetryRequest({
|
||||
verb: HttpVerb.Get,
|
||||
url,
|
||||
})
|
||||
}
|
||||
return this.errorResponseWithFallbackMessage(errorResponse, API_MESSAGE_GENERIC_SYNC_FAIL)
|
||||
})
|
||||
this.processResponse(response)
|
||||
const path = Paths.v1.sessions
|
||||
const response = await this.httpService.get<SessionListResponse>(path, {}, this.getSessionAccessToken())
|
||||
|
||||
if (isErrorResponse(response)) {
|
||||
this.preprocessAuthenticatedErrorResponse(response)
|
||||
return this.errorResponseWithFallbackMessage(response, API_MESSAGE_GENERIC_SYNC_FAIL)
|
||||
}
|
||||
|
||||
this.processSuccessResponseForMetaBody(response)
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
async deleteSession(sessionId: UuidString): Promise<Responses.HttpResponse> {
|
||||
async deleteSession(sessionId: UuidString): Promise<HttpResponse<SessionListResponse>> {
|
||||
const preprocessingError = this.preprocessingError()
|
||||
if (preprocessingError) {
|
||||
return preprocessingError
|
||||
}
|
||||
const url = joinPaths(this.host, <string>Paths.v1.session(sessionId))
|
||||
const response: Responses.SessionListResponse | Responses.HttpResponse = await this.httpService
|
||||
.deleteAbsolute(url, { uuid: sessionId }, this.getSessionAccessToken())
|
||||
.catch((error: Responses.HttpResponse) => {
|
||||
const errorResponse = error as Responses.HttpResponse
|
||||
this.preprocessAuthenticatedErrorResponse(errorResponse)
|
||||
if (Responses.isErrorResponseExpiredToken(errorResponse)) {
|
||||
return this.refreshSessionThenRetryRequest({
|
||||
verb: HttpVerb.Delete,
|
||||
url,
|
||||
})
|
||||
}
|
||||
return this.errorResponseWithFallbackMessage(errorResponse, API_MESSAGE_GENERIC_SYNC_FAIL)
|
||||
})
|
||||
this.processResponse(response)
|
||||
const path = Paths.v1.session(sessionId)
|
||||
const response = await this.httpService.delete<SessionListResponse>(
|
||||
path,
|
||||
{ uuid: sessionId },
|
||||
this.getSessionAccessToken(),
|
||||
)
|
||||
|
||||
if (isErrorResponse(response)) {
|
||||
this.preprocessAuthenticatedErrorResponse(response)
|
||||
return this.errorResponseWithFallbackMessage(response, API_MESSAGE_GENERIC_SYNC_FAIL)
|
||||
}
|
||||
|
||||
this.processSuccessResponseForMetaBody(response)
|
||||
return response
|
||||
}
|
||||
|
||||
async getUserFeatures(userUuid: UuidString): Promise<Responses.HttpResponse | Responses.UserFeaturesResponse> {
|
||||
const url = joinPaths(this.host, Paths.v1.userFeatures(userUuid))
|
||||
const response = await this.httpService
|
||||
.getAbsolute(url, undefined, this.getSessionAccessToken())
|
||||
.catch((errorResponse: Responses.HttpResponse) => {
|
||||
this.preprocessAuthenticatedErrorResponse(errorResponse)
|
||||
if (Responses.isErrorResponseExpiredToken(errorResponse)) {
|
||||
return this.refreshSessionThenRetryRequest({
|
||||
verb: HttpVerb.Get,
|
||||
url,
|
||||
})
|
||||
}
|
||||
return this.errorResponseWithFallbackMessage(errorResponse, API_MESSAGE_GENERIC_SYNC_FAIL)
|
||||
})
|
||||
this.processResponse(response)
|
||||
async getUserFeatures(userUuid: UuidString): Promise<HttpResponse<UserFeaturesResponse>> {
|
||||
const path = Paths.v1.userFeatures(userUuid)
|
||||
const response = await this.httpService.get<UserFeaturesResponse>(path, undefined, this.getSessionAccessToken())
|
||||
|
||||
if (isErrorResponse(response)) {
|
||||
this.preprocessAuthenticatedErrorResponse(response)
|
||||
return this.errorResponseWithFallbackMessage(response, API_MESSAGE_GENERIC_SYNC_FAIL)
|
||||
}
|
||||
|
||||
this.processSuccessResponseForMetaBody(response)
|
||||
return response
|
||||
}
|
||||
|
||||
private async tokenRefreshableRequest<T extends Responses.MinimalHttpResponse>(
|
||||
private async tokenRefreshableRequest<T>(
|
||||
params: HttpRequest & { fallbackErrorMessage: string },
|
||||
): Promise<T> {
|
||||
): Promise<HttpResponse<T>> {
|
||||
const preprocessingError = this.preprocessingError()
|
||||
if (preprocessingError) {
|
||||
return preprocessingError as T
|
||||
return preprocessingError
|
||||
}
|
||||
const response: T | Responses.HttpResponse = await this.httpService
|
||||
.runHttp(params)
|
||||
.catch((errorResponse: Responses.HttpResponse) => {
|
||||
this.preprocessAuthenticatedErrorResponse(errorResponse)
|
||||
if (Responses.isErrorResponseExpiredToken(errorResponse)) {
|
||||
return this.refreshSessionThenRetryRequest(params)
|
||||
}
|
||||
return this.errorResponseWithFallbackMessage(errorResponse, params.fallbackErrorMessage)
|
||||
})
|
||||
this.processResponse(response)
|
||||
return response as T
|
||||
|
||||
const response = await this.httpService.runHttp<T>(params)
|
||||
|
||||
if (isErrorResponse(response)) {
|
||||
this.preprocessAuthenticatedErrorResponse(response)
|
||||
return this.errorResponseWithFallbackMessage(response, params.fallbackErrorMessage)
|
||||
}
|
||||
|
||||
this.processSuccessResponseForMetaBody(response)
|
||||
return response
|
||||
}
|
||||
|
||||
async listSettings(userUuid: UuidString): Promise<Responses.ListSettingsResponse> {
|
||||
return await this.tokenRefreshableRequest<Responses.ListSettingsResponse>({
|
||||
async listSettings(userUuid: UuidString): Promise<HttpResponse<ListSettingsResponse>> {
|
||||
return await this.tokenRefreshableRequest<ListSettingsResponse>({
|
||||
verb: HttpVerb.Get,
|
||||
url: joinPaths(this.host, Paths.v1.settings(userUuid)),
|
||||
fallbackErrorMessage: API_MESSAGE_FAILED_GET_SETTINGS,
|
||||
@@ -566,13 +553,13 @@ export class SNApiService
|
||||
settingName: string,
|
||||
settingValue: string | null,
|
||||
sensitive: boolean,
|
||||
): Promise<Responses.UpdateSettingResponse> {
|
||||
): Promise<HttpResponse<UpdateSettingResponse>> {
|
||||
const params = {
|
||||
name: settingName,
|
||||
value: settingValue,
|
||||
sensitive: sensitive,
|
||||
}
|
||||
return this.tokenRefreshableRequest<Responses.UpdateSettingResponse>({
|
||||
return this.tokenRefreshableRequest<UpdateSettingResponse>({
|
||||
verb: HttpVerb.Put,
|
||||
url: joinPaths(this.host, Paths.v1.settings(userUuid)),
|
||||
authentication: this.getSessionAccessToken(),
|
||||
@@ -581,8 +568,8 @@ export class SNApiService
|
||||
})
|
||||
}
|
||||
|
||||
async getSetting(userUuid: UuidString, settingName: SettingName): Promise<Responses.GetSettingResponse> {
|
||||
return await this.tokenRefreshableRequest<Responses.GetSettingResponse>({
|
||||
async getSetting(userUuid: UuidString, settingName: SettingName): Promise<HttpResponse<GetSettingResponse>> {
|
||||
return await this.tokenRefreshableRequest<GetSettingResponse>({
|
||||
verb: HttpVerb.Get,
|
||||
url: joinPaths(this.host, Paths.v1.setting(userUuid, settingName.toLowerCase() as SettingName)),
|
||||
authentication: this.getSessionAccessToken(),
|
||||
@@ -593,8 +580,8 @@ export class SNApiService
|
||||
async getSubscriptionSetting(
|
||||
userUuid: UuidString,
|
||||
settingName: SubscriptionSettingName,
|
||||
): Promise<Responses.GetSettingResponse> {
|
||||
return await this.tokenRefreshableRequest<Responses.GetSettingResponse>({
|
||||
): Promise<HttpResponse<GetSettingResponse>> {
|
||||
return await this.tokenRefreshableRequest<GetSettingResponse>({
|
||||
verb: HttpVerb.Get,
|
||||
url: joinPaths(
|
||||
this.host,
|
||||
@@ -605,8 +592,8 @@ export class SNApiService
|
||||
})
|
||||
}
|
||||
|
||||
async deleteSetting(userUuid: UuidString, settingName: SettingName): Promise<Responses.DeleteSettingResponse> {
|
||||
return this.tokenRefreshableRequest<Responses.DeleteSettingResponse>({
|
||||
async deleteSetting(userUuid: UuidString, settingName: SettingName): Promise<HttpResponse<DeleteSettingResponse>> {
|
||||
return this.tokenRefreshableRequest<DeleteSettingResponse>({
|
||||
verb: HttpVerb.Delete,
|
||||
url: joinPaths(this.host, Paths.v1.setting(userUuid, settingName)),
|
||||
authentication: this.getSessionAccessToken(),
|
||||
@@ -614,7 +601,7 @@ export class SNApiService
|
||||
})
|
||||
}
|
||||
|
||||
public downloadFeatureUrl(url: string): Promise<Responses.HttpResponse> {
|
||||
public downloadFeatureUrl(url: string): Promise<HttpResponse> {
|
||||
return this.request({
|
||||
verb: HttpVerb.Get,
|
||||
url,
|
||||
@@ -623,38 +610,39 @@ export class SNApiService
|
||||
})
|
||||
}
|
||||
|
||||
public async getSubscription(userUuid: string): Promise<Responses.HttpResponse | Responses.GetSubscriptionResponse> {
|
||||
public async getSubscription(userUuid: string): Promise<HttpResponse<GetSubscriptionResponse>> {
|
||||
const url = joinPaths(this.host, Paths.v1.subscription(userUuid))
|
||||
const response = await this.tokenRefreshableRequest({
|
||||
return this.tokenRefreshableRequest({
|
||||
verb: HttpVerb.Get,
|
||||
url,
|
||||
authentication: this.getSessionAccessToken(),
|
||||
fallbackErrorMessage: API_MESSAGE_FAILED_SUBSCRIPTION_INFO,
|
||||
})
|
||||
return response
|
||||
}
|
||||
|
||||
public async getAvailableSubscriptions(): Promise<
|
||||
Responses.HttpResponse | Responses.GetAvailableSubscriptionsResponse
|
||||
> {
|
||||
public async getAvailableSubscriptions(): Promise<HttpResponse<GetAvailableSubscriptionsResponse>> {
|
||||
const url = joinPaths(this.host, Paths.v2.subscriptions)
|
||||
const response = await this.request({
|
||||
return this.request({
|
||||
verb: HttpVerb.Get,
|
||||
url,
|
||||
fallbackErrorMessage: API_MESSAGE_FAILED_SUBSCRIPTION_INFO,
|
||||
})
|
||||
return response
|
||||
}
|
||||
|
||||
public async getNewSubscriptionToken(): Promise<string | undefined> {
|
||||
const url = joinPaths(this.host, Paths.v1.subscriptionTokens)
|
||||
const response: Responses.HttpResponse | Responses.PostSubscriptionTokensResponse = await this.request({
|
||||
const response = await this.request<PostSubscriptionTokensResponse>({
|
||||
verb: HttpVerb.Post,
|
||||
url,
|
||||
authentication: this.getSessionAccessToken(),
|
||||
fallbackErrorMessage: API_MESSAGE_FAILED_ACCESS_PURCHASE,
|
||||
})
|
||||
return (response as Responses.PostSubscriptionTokensResponse).data?.token
|
||||
|
||||
if (isErrorResponse(response)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return response.data.token
|
||||
}
|
||||
|
||||
public async downloadOfflineFeaturesFromRepo(
|
||||
@@ -673,17 +661,17 @@ export class SNApiService
|
||||
return new ClientDisplayableError('This offline features host is not in the trusted allowlist.')
|
||||
}
|
||||
|
||||
const response: Responses.HttpResponse | Responses.GetOfflineFeaturesResponse = await this.request({
|
||||
const response = await this.request<GetOfflineFeaturesResponse>({
|
||||
verb: HttpVerb.Get,
|
||||
url: featuresUrl,
|
||||
fallbackErrorMessage: API_MESSAGE_FAILED_OFFLINE_FEATURES,
|
||||
customHeaders: [{ key: 'x-offline-token', value: extensionKey }],
|
||||
})
|
||||
|
||||
if (response.error) {
|
||||
return ClientDisplayableError.FromError(response.error)
|
||||
if (isErrorResponse(response)) {
|
||||
return ClientDisplayableError.FromError(response.data.error)
|
||||
}
|
||||
const data = (response as Responses.GetOfflineFeaturesResponse).data
|
||||
const data = response.data
|
||||
return {
|
||||
features: data?.features || [],
|
||||
roles: data?.roles || [],
|
||||
@@ -693,11 +681,11 @@ export class SNApiService
|
||||
}
|
||||
}
|
||||
|
||||
public async registerForListedAccount(): Promise<Responses.ListedRegistrationResponse> {
|
||||
public async registerForListedAccount(): Promise<HttpResponse<ListedRegistrationResponse>> {
|
||||
if (!this.user) {
|
||||
throw Error('Cannot register for Listed without user account.')
|
||||
}
|
||||
return await this.tokenRefreshableRequest<Responses.ListedRegistrationResponse>({
|
||||
return this.tokenRefreshableRequest<ListedRegistrationResponse>({
|
||||
verb: HttpVerb.Post,
|
||||
url: joinPaths(this.host, Paths.v1.listedRegistration(this.user.uuid)),
|
||||
fallbackErrorMessage: API_MESSAGE_FAILED_LISTED_REGISTRATION,
|
||||
@@ -717,7 +705,7 @@ export class SNApiService
|
||||
resources: [{ remoteIdentifier, unencryptedFileSize: unencryptedFileSize || 0 }],
|
||||
}
|
||||
|
||||
const response = await this.tokenRefreshableRequest<Responses.CreateValetTokenResponse>({
|
||||
const response = await this.tokenRefreshableRequest<CreateValetTokenResponse>({
|
||||
verb: HttpVerb.Post,
|
||||
url: url,
|
||||
authentication: this.getSessionAccessToken(),
|
||||
@@ -725,6 +713,10 @@ export class SNApiService
|
||||
params,
|
||||
})
|
||||
|
||||
if (isErrorResponse(response)) {
|
||||
return new ClientDisplayableError(response.data?.error?.message as string)
|
||||
}
|
||||
|
||||
if (!response.data?.success) {
|
||||
return new ClientDisplayableError(response.data?.reason as string, undefined, response.data?.reason as string)
|
||||
}
|
||||
@@ -732,30 +724,26 @@ export class SNApiService
|
||||
return response.data?.valetToken
|
||||
}
|
||||
|
||||
public async startUploadSession(apiToken: string): Promise<Responses.StartUploadSessionResponse> {
|
||||
public async startUploadSession(apiToken: string): Promise<HttpResponse<StartUploadSessionResponse>> {
|
||||
const url = joinPaths(this.getFilesHost(), Paths.v1.startUploadSession)
|
||||
|
||||
const response: Responses.HttpResponse | Responses.StartUploadSessionResponse = await this.tokenRefreshableRequest({
|
||||
return this.tokenRefreshableRequest({
|
||||
verb: HttpVerb.Post,
|
||||
url,
|
||||
customHeaders: [{ key: 'x-valet-token', value: apiToken }],
|
||||
fallbackErrorMessage: Strings.Network.Files.FailedStartUploadSession,
|
||||
})
|
||||
|
||||
return response as Responses.StartUploadSessionResponse
|
||||
}
|
||||
|
||||
public async deleteFile(apiToken: string): Promise<Responses.MinimalHttpResponse> {
|
||||
public async deleteFile(apiToken: string): Promise<HttpResponse<StartUploadSessionResponse>> {
|
||||
const url = joinPaths(this.getFilesHost(), Paths.v1.deleteFile)
|
||||
|
||||
const response: Responses.HttpResponse | Responses.StartUploadSessionResponse = await this.tokenRefreshableRequest({
|
||||
return this.tokenRefreshableRequest({
|
||||
verb: HttpVerb.Delete,
|
||||
url,
|
||||
customHeaders: [{ key: 'x-valet-token', value: apiToken }],
|
||||
fallbackErrorMessage: Strings.Network.Files.FailedDeleteFile,
|
||||
})
|
||||
|
||||
return response as Responses.MinimalHttpResponse
|
||||
}
|
||||
|
||||
public async uploadFileBytes(apiToken: string, chunkId: number, encryptedBytes: Uint8Array): Promise<boolean> {
|
||||
@@ -764,7 +752,7 @@ export class SNApiService
|
||||
}
|
||||
const url = joinPaths(this.getFilesHost(), Paths.v1.uploadFileChunk)
|
||||
|
||||
const response: Responses.HttpResponse | Responses.UploadFileChunkResponse = await this.tokenRefreshableRequest({
|
||||
const response = await this.tokenRefreshableRequest<UploadFileChunkResponse>({
|
||||
verb: HttpVerb.Post,
|
||||
url,
|
||||
rawBytes: encryptedBytes,
|
||||
@@ -776,20 +764,28 @@ export class SNApiService
|
||||
fallbackErrorMessage: Strings.Network.Files.FailedUploadFileChunk,
|
||||
})
|
||||
|
||||
return (response as Responses.UploadFileChunkResponse).success
|
||||
if (isErrorResponse(response)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return response.data.success
|
||||
}
|
||||
|
||||
public async closeUploadSession(apiToken: string): Promise<boolean> {
|
||||
const url = joinPaths(this.getFilesHost(), Paths.v1.closeUploadSession)
|
||||
|
||||
const response: Responses.HttpResponse | Responses.CloseUploadSessionResponse = await this.tokenRefreshableRequest({
|
||||
const response = await this.tokenRefreshableRequest<CloseUploadSessionResponse>({
|
||||
verb: HttpVerb.Post,
|
||||
url,
|
||||
customHeaders: [{ key: 'x-valet-token', value: apiToken }],
|
||||
fallbackErrorMessage: Strings.Network.Files.FailedCloseUploadSession,
|
||||
})
|
||||
|
||||
return (response as Responses.CloseUploadSessionResponse).success
|
||||
if (isErrorResponse(response)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return response.data.success
|
||||
}
|
||||
|
||||
public getFilesDownloadUrl(): string {
|
||||
@@ -806,21 +802,24 @@ export class SNApiService
|
||||
const url = this.getFilesDownloadUrl()
|
||||
const pullChunkSize = file.encryptedChunkSizes[chunkIndex]
|
||||
|
||||
const response: Responses.HttpResponse | Responses.DownloadFileChunkResponse =
|
||||
await this.tokenRefreshableRequest<Responses.DownloadFileChunkResponse>({
|
||||
verb: HttpVerb.Get,
|
||||
url,
|
||||
customHeaders: [
|
||||
{ key: 'x-valet-token', value: apiToken },
|
||||
{
|
||||
key: 'x-chunk-size',
|
||||
value: pullChunkSize.toString(),
|
||||
},
|
||||
{ key: 'range', value: `bytes=${contentRangeStart}-` },
|
||||
],
|
||||
fallbackErrorMessage: Strings.Network.Files.FailedDownloadFileChunk,
|
||||
responseType: 'arraybuffer',
|
||||
})
|
||||
const response = await this.tokenRefreshableRequest<DownloadFileChunkResponse>({
|
||||
verb: HttpVerb.Get,
|
||||
url,
|
||||
customHeaders: [
|
||||
{ key: 'x-valet-token', value: apiToken },
|
||||
{
|
||||
key: 'x-chunk-size',
|
||||
value: pullChunkSize.toString(),
|
||||
},
|
||||
{ key: 'range', value: `bytes=${contentRangeStart}-` },
|
||||
],
|
||||
fallbackErrorMessage: Strings.Network.Files.FailedDownloadFileChunk,
|
||||
responseType: 'arraybuffer',
|
||||
})
|
||||
|
||||
if (isErrorResponse(response)) {
|
||||
return new ClientDisplayableError(response.data?.error?.message as string)
|
||||
}
|
||||
|
||||
const contentRangeHeader = (<Map<string, string | null>>response.headers).get('content-range')
|
||||
if (!contentRangeHeader) {
|
||||
@@ -836,7 +835,7 @@ export class SNApiService
|
||||
const rangeEnd = +matches[3]
|
||||
const totalSize = +matches[4]
|
||||
|
||||
const bytesReceived = new Uint8Array(response.data as ArrayBuffer)
|
||||
const bytesReceived = new Uint8Array(response.data)
|
||||
|
||||
await onBytesReceived(bytesReceived)
|
||||
|
||||
@@ -847,8 +846,8 @@ export class SNApiService
|
||||
return undefined
|
||||
}
|
||||
|
||||
async checkIntegrity(integrityPayloads: Responses.IntegrityPayload[]): Promise<Responses.CheckIntegrityResponse> {
|
||||
return await this.tokenRefreshableRequest<Responses.CheckIntegrityResponse>({
|
||||
async checkIntegrity(integrityPayloads: IntegrityPayload[]): Promise<HttpResponse<CheckIntegrityResponse>> {
|
||||
return this.tokenRefreshableRequest<CheckIntegrityResponse>({
|
||||
verb: HttpVerb.Post,
|
||||
url: joinPaths(this.host, Paths.v1.checkIntegrity),
|
||||
params: {
|
||||
@@ -859,8 +858,8 @@ export class SNApiService
|
||||
})
|
||||
}
|
||||
|
||||
async getSingleItem(itemUuid: string): Promise<Responses.GetSingleItemResponse> {
|
||||
return await this.tokenRefreshableRequest<Responses.GetSingleItemResponse>({
|
||||
async getSingleItem(itemUuid: string): Promise<HttpResponse<GetSingleItemResponse>> {
|
||||
return this.tokenRefreshableRequest<GetSingleItemResponse>({
|
||||
verb: HttpVerb.Get,
|
||||
url: joinPaths(this.host, Paths.v1.getSingleItem(itemUuid)),
|
||||
fallbackErrorMessage: API_MESSAGE_GENERIC_SINGLE_ITEM_SYNC_FAIL,
|
||||
@@ -870,18 +869,19 @@ export class SNApiService
|
||||
|
||||
private preprocessingError() {
|
||||
if (this.refreshingSession) {
|
||||
return this.createErrorResponse(API_MESSAGE_TOKEN_REFRESH_IN_PROGRESS)
|
||||
return this.createErrorResponse(API_MESSAGE_TOKEN_REFRESH_IN_PROGRESS, HttpStatusCode.BadRequest)
|
||||
}
|
||||
|
||||
if (!this.session) {
|
||||
return this.createErrorResponse(API_MESSAGE_INVALID_SESSION)
|
||||
return this.createErrorResponse(API_MESSAGE_INVALID_SESSION, HttpStatusCode.BadRequest)
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
/** Handle errored responses to authenticated requests */
|
||||
private preprocessAuthenticatedErrorResponse(response: Responses.HttpResponse) {
|
||||
if (response.status === Responses.StatusCode.HttpStatusInvalidSession && this.session) {
|
||||
this.invalidSessionObserver?.(response.error?.tag === ErrorTag.RevokedSession)
|
||||
private preprocessAuthenticatedErrorResponse(response: HttpResponse) {
|
||||
if (response.status === HttpStatusCode.Unauthorized && this.session) {
|
||||
this.invalidSessionObserver?.(response.data.error?.tag === ErrorTag.RevokedSession)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { HttpResponse, StatusCode } from '@standardnotes/responses'
|
||||
import {
|
||||
DeprecatedHttpResponse,
|
||||
DeprecatedStatusCode,
|
||||
HttpRequestParams,
|
||||
HttpVerb,
|
||||
HttpRequest,
|
||||
} from '@standardnotes/responses'
|
||||
import { isString } from '@standardnotes/utils'
|
||||
import { SnjsVersion } from '@Lib/Version'
|
||||
import {
|
||||
@@ -9,33 +15,12 @@ import {
|
||||
} from '@standardnotes/services'
|
||||
import { Environment } from '@standardnotes/models'
|
||||
|
||||
export enum HttpVerb {
|
||||
Get = 'GET',
|
||||
Post = 'POST',
|
||||
Put = 'PUT',
|
||||
Patch = 'PATCH',
|
||||
Delete = 'DELETE',
|
||||
}
|
||||
|
||||
const REQUEST_READY_STATE_COMPLETED = 4
|
||||
|
||||
export type HttpParams = Record<string, unknown>
|
||||
|
||||
export type HttpRequest = {
|
||||
url: string
|
||||
params?: HttpParams
|
||||
rawBytes?: Uint8Array
|
||||
verb: HttpVerb
|
||||
authentication?: string
|
||||
customHeaders?: Record<string, string>[]
|
||||
responseType?: XMLHttpRequestResponseType
|
||||
external?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* A non-SNJS specific wrapper for XMLHttpRequests
|
||||
*/
|
||||
export class SNHttpService extends AbstractService {
|
||||
export class DeprecatedHttpService extends AbstractService {
|
||||
constructor(
|
||||
private readonly environment: Environment,
|
||||
private readonly appVersion: string,
|
||||
@@ -44,27 +29,47 @@ export class SNHttpService extends AbstractService {
|
||||
super(internalEventBus)
|
||||
}
|
||||
|
||||
public async getAbsolute(url: string, params?: HttpParams, authentication?: string): Promise<HttpResponse> {
|
||||
public async getAbsolute(
|
||||
url: string,
|
||||
params?: HttpRequestParams,
|
||||
authentication?: string,
|
||||
): Promise<DeprecatedHttpResponse> {
|
||||
return this.runHttp({ url, params, verb: HttpVerb.Get, authentication })
|
||||
}
|
||||
|
||||
public async postAbsolute(url: string, params?: HttpParams, authentication?: string): Promise<HttpResponse> {
|
||||
public async postAbsolute(
|
||||
url: string,
|
||||
params?: HttpRequestParams,
|
||||
authentication?: string,
|
||||
): Promise<DeprecatedHttpResponse> {
|
||||
return this.runHttp({ url, params, verb: HttpVerb.Post, authentication })
|
||||
}
|
||||
|
||||
public async putAbsolute(url: string, params?: HttpParams, authentication?: string): Promise<HttpResponse> {
|
||||
public async putAbsolute(
|
||||
url: string,
|
||||
params?: HttpRequestParams,
|
||||
authentication?: string,
|
||||
): Promise<DeprecatedHttpResponse> {
|
||||
return this.runHttp({ url, params, verb: HttpVerb.Put, authentication })
|
||||
}
|
||||
|
||||
public async patchAbsolute(url: string, params: HttpParams, authentication?: string): Promise<HttpResponse> {
|
||||
public async patchAbsolute(
|
||||
url: string,
|
||||
params: HttpRequestParams,
|
||||
authentication?: string,
|
||||
): Promise<DeprecatedHttpResponse> {
|
||||
return this.runHttp({ url, params, verb: HttpVerb.Patch, authentication })
|
||||
}
|
||||
|
||||
public async deleteAbsolute(url: string, params?: HttpParams, authentication?: string): Promise<HttpResponse> {
|
||||
public async deleteAbsolute(
|
||||
url: string,
|
||||
params?: HttpRequestParams,
|
||||
authentication?: string,
|
||||
): Promise<DeprecatedHttpResponse> {
|
||||
return this.runHttp({ url, params, verb: HttpVerb.Delete, authentication })
|
||||
}
|
||||
|
||||
public async runHttp(httpRequest: HttpRequest): Promise<HttpResponse> {
|
||||
public async runHttp(httpRequest: HttpRequest): Promise<DeprecatedHttpResponse> {
|
||||
const request = this.createXmlRequest(httpRequest)
|
||||
|
||||
return this.runRequest(request, this.createRequestBody(httpRequest))
|
||||
@@ -84,7 +89,7 @@ export class SNHttpService extends AbstractService {
|
||||
private createXmlRequest(httpRequest: HttpRequest) {
|
||||
const request = new XMLHttpRequest()
|
||||
if (httpRequest.params && httpRequest.verb === HttpVerb.Get && Object.keys(httpRequest.params).length > 0) {
|
||||
httpRequest.url = this.urlForUrlAndParams(httpRequest.url, httpRequest.params)
|
||||
httpRequest.url = this.urlForUrlAndParams(httpRequest.url, httpRequest.params as Record<string, unknown>)
|
||||
}
|
||||
request.open(httpRequest.verb, httpRequest.url, true)
|
||||
request.responseType = httpRequest.responseType ?? ''
|
||||
@@ -116,7 +121,7 @@ export class SNHttpService extends AbstractService {
|
||||
return request
|
||||
}
|
||||
|
||||
private async runRequest(request: XMLHttpRequest, body?: string | Uint8Array): Promise<HttpResponse> {
|
||||
private async runRequest(request: XMLHttpRequest, body?: string | Uint8Array): Promise<DeprecatedHttpResponse> {
|
||||
return new Promise((resolve, reject) => {
|
||||
request.onreadystatechange = () => {
|
||||
this.stateChangeHandlerForRequest(request, resolve, reject)
|
||||
@@ -127,14 +132,14 @@ export class SNHttpService extends AbstractService {
|
||||
|
||||
private stateChangeHandlerForRequest(
|
||||
request: XMLHttpRequest,
|
||||
resolve: (response: HttpResponse) => void,
|
||||
reject: (response: HttpResponse) => void,
|
||||
resolve: (response: DeprecatedHttpResponse) => void,
|
||||
reject: (response: DeprecatedHttpResponse) => void,
|
||||
) {
|
||||
if (request.readyState !== REQUEST_READY_STATE_COMPLETED) {
|
||||
return
|
||||
}
|
||||
const httpStatus = request.status
|
||||
const response: HttpResponse = {
|
||||
const response: DeprecatedHttpResponse = {
|
||||
status: httpStatus,
|
||||
headers: new Map<string, string | null>(),
|
||||
}
|
||||
@@ -152,7 +157,7 @@ export class SNHttpService extends AbstractService {
|
||||
})
|
||||
|
||||
try {
|
||||
if (httpStatus !== StatusCode.HttpStatusNoContent) {
|
||||
if (httpStatus !== DeprecatedStatusCode.HttpStatusNoContent) {
|
||||
let body
|
||||
|
||||
const contentTypeHeader = response.headers?.get('content-type') || response.headers?.get('Content-Type')
|
||||
@@ -177,10 +182,13 @@ export class SNHttpService extends AbstractService {
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
if (httpStatus >= StatusCode.HttpStatusMinSuccess && httpStatus <= StatusCode.HttpStatusMaxSuccess) {
|
||||
if (
|
||||
httpStatus >= DeprecatedStatusCode.HttpStatusMinSuccess &&
|
||||
httpStatus <= DeprecatedStatusCode.HttpStatusMaxSuccess
|
||||
) {
|
||||
resolve(response)
|
||||
} else {
|
||||
if (httpStatus === StatusCode.HttpStatusForbidden) {
|
||||
if (httpStatus === DeprecatedStatusCode.HttpStatusForbidden) {
|
||||
response.error = {
|
||||
message: API_MESSAGE_RATE_LIMITED,
|
||||
status: httpStatus,
|
||||
@@ -200,7 +208,7 @@ export class SNHttpService extends AbstractService {
|
||||
}
|
||||
}
|
||||
|
||||
private urlForUrlAndParams(url: string, params: HttpParams) {
|
||||
private urlForUrlAndParams(url: string, params: Record<string, unknown>) {
|
||||
const keyValueString = Object.keys(params)
|
||||
.map((key) => {
|
||||
return key + '=' + encodeURIComponent(params[key] as string)
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isErrorResponse } from '@standardnotes/responses'
|
||||
import { UserRolesChangedEvent } from '@standardnotes/domain-events'
|
||||
import { AbstractService, InternalEventBusInterface, StorageKey } from '@standardnotes/services'
|
||||
import { WebSocketApiServiceInterface } from '@standardnotes/api'
|
||||
@@ -72,7 +73,7 @@ export class SNWebSocketsService extends AbstractService<WebSocketsServiceEvent,
|
||||
private async createWebSocketConnectionToken(): Promise<string | undefined> {
|
||||
try {
|
||||
const response = await this.webSocketApiService.createConnectionToken()
|
||||
if (response.data.error) {
|
||||
if (isErrorResponse(response)) {
|
||||
console.error(response.data.error)
|
||||
|
||||
return undefined
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export * from './ApiService'
|
||||
export * from './HttpService'
|
||||
export * from './DeprecatedHttpService'
|
||||
export * from './Paths'
|
||||
export * from '../Session/SessionManager'
|
||||
export * from './WebsocketsService'
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
lastElement,
|
||||
isString,
|
||||
} from '@standardnotes/utils'
|
||||
import { ClientDisplayableError, UserFeaturesResponse } from '@standardnotes/responses'
|
||||
import { ClientDisplayableError, isErrorResponse } from '@standardnotes/responses'
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { RoleName } from '@standardnotes/domain-core'
|
||||
import { FillItemContent, PayloadEmitSource } from '@standardnotes/models'
|
||||
@@ -417,8 +417,8 @@ export class SNFeaturesService
|
||||
if (shouldDownloadRoleBasedFeatures) {
|
||||
const featuresResponse = await this.apiService.getUserFeatures(userUuid)
|
||||
|
||||
if (!featuresResponse.error && featuresResponse.data && !this.deinited) {
|
||||
const features = (featuresResponse as UserFeaturesResponse).data.features
|
||||
if (!isErrorResponse(featuresResponse) && !this.deinited) {
|
||||
const features = featuresResponse.data.features
|
||||
await this.didDownloadFeatures(features)
|
||||
}
|
||||
}
|
||||
@@ -747,7 +747,7 @@ export class SNFeaturesService
|
||||
|
||||
private async performDownloadExternalFeature(url: string): Promise<Models.SNComponent | undefined> {
|
||||
const response = await this.apiService.downloadFeatureUrl(url)
|
||||
if (response.error) {
|
||||
if (response.data?.error) {
|
||||
await this.alertService.alert(API_MESSAGE_FAILED_DOWNLOADING_EXTENSION)
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import { SNApiService } from '@Lib/Services/Api/ApiService'
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { ItemManager } from '../Items/ItemManager'
|
||||
import { removeFromArray, Uuids } from '@standardnotes/utils'
|
||||
import { ClientDisplayableError, KeyParamsResponse } from '@standardnotes/responses'
|
||||
import { ClientDisplayableError, isErrorResponse } from '@standardnotes/responses'
|
||||
import {
|
||||
AlertService,
|
||||
AbstractService,
|
||||
@@ -283,7 +283,7 @@ export class SNKeyRecoveryService extends AbstractService<KeyRecoveryEvent, Decr
|
||||
|
||||
const signInResponse = await this.userService.correctiveSignIn(rootKey)
|
||||
|
||||
if (!signInResponse.error) {
|
||||
if (!isErrorResponse(signInResponse)) {
|
||||
void this.alertService.alert(KeyRecoveryStrings.KeyRecoveryRootKeyReplaced)
|
||||
|
||||
return rootKey
|
||||
@@ -335,8 +335,8 @@ export class SNKeyRecoveryService extends AbstractService<KeyRecoveryEvent, Decr
|
||||
email: identifier,
|
||||
})
|
||||
|
||||
if (!paramsResponse.error && paramsResponse.data) {
|
||||
return KeyParamsFromApiResponse(paramsResponse as KeyParamsResponse)
|
||||
if (!isErrorResponse(paramsResponse)) {
|
||||
return KeyParamsFromApiResponse(paramsResponse.data)
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ import { isString, lastElement, sleep } from '@standardnotes/utils'
|
||||
import { UuidString } from '@Lib/Types/UuidString'
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { ItemManager } from '@Lib/Services/Items/ItemManager'
|
||||
import { SNHttpService } from '../Api/HttpService'
|
||||
import { DeprecatedHttpService } from '../Api/DeprecatedHttpService'
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
import { SNSettingsService } from '../Settings/SNSettingsService'
|
||||
import { ListedClientInterface } from './ListedClientInterface'
|
||||
import { SNApiService } from '../Api/ApiService'
|
||||
import { ListedAccount, ListedAccountInfo, ListedAccountInfoResponse } from '@standardnotes/responses'
|
||||
import { isErrorResponse, ListedAccount, ListedAccountInfo, ListedAccountInfoResponse } from '@standardnotes/responses'
|
||||
import { NoteMutator, SNActionsExtension, SNNote } from '@standardnotes/models'
|
||||
import { AbstractService, InternalEventBusInterface, MutatorClientInterface } from '@standardnotes/services'
|
||||
import { SNProtectionService } from '../Protection'
|
||||
@@ -17,7 +17,7 @@ export class ListedService extends AbstractService implements ListedClientInterf
|
||||
private apiService: SNApiService,
|
||||
private itemManager: ItemManager,
|
||||
private settingsService: SNSettingsService,
|
||||
private httpSerivce: SNHttpService,
|
||||
private httpSerivce: DeprecatedHttpService,
|
||||
private protectionService: SNProtectionService,
|
||||
private mutatorService: MutatorClientInterface,
|
||||
protected override internalEventBus: InternalEventBusInterface,
|
||||
@@ -63,7 +63,7 @@ export class ListedService extends AbstractService implements ListedClientInterf
|
||||
public async requestNewListedAccount(): Promise<ListedAccount | undefined> {
|
||||
const accountsBeforeRequest = await this.getSettingsBasedListedAccounts()
|
||||
const response = await this.apiService.registerForListedAccount()
|
||||
if (response.error) {
|
||||
if (isErrorResponse(response)) {
|
||||
return undefined
|
||||
}
|
||||
const MaxAttempts = 4
|
||||
@@ -99,11 +99,12 @@ export class ListedService extends AbstractService implements ListedClientInterf
|
||||
const response = (await this.httpSerivce.getAbsolute(url).catch((error) => {
|
||||
console.error(error)
|
||||
})) as ListedAccountInfoResponse
|
||||
if (!response || response.error || !response.data || isString(response.data)) {
|
||||
|
||||
if (!response || response.data?.error || !response.data || isString(response.data)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return response.data
|
||||
return response
|
||||
}
|
||||
|
||||
private async getSettingsBasedListedAccounts(): Promise<ListedAccount[]> {
|
||||
|
||||
@@ -28,16 +28,28 @@ import {
|
||||
SessionRefreshedData,
|
||||
} from '@standardnotes/services'
|
||||
import { Base64String } from '@standardnotes/sncrypto-common'
|
||||
import { ClientDisplayableError, SessionBody } from '@standardnotes/responses'
|
||||
import {
|
||||
ClientDisplayableError,
|
||||
SessionBody,
|
||||
ErrorTag,
|
||||
HttpResponse,
|
||||
isErrorResponse,
|
||||
SessionListEntry,
|
||||
User,
|
||||
AvailableSubscriptions,
|
||||
KeyParamsResponse,
|
||||
SignInResponse,
|
||||
ChangeCredentialsResponse,
|
||||
SessionListResponse,
|
||||
HttpSuccessResponse,
|
||||
} from '@standardnotes/responses'
|
||||
import { CopyPayloadWithContentOverride } from '@standardnotes/models'
|
||||
import { isNullOrUndefined } from '@standardnotes/utils'
|
||||
import { LegacySession, MapperInterface, Session, SessionToken } from '@standardnotes/domain-core'
|
||||
import { KeyParamsFromApiResponse, SNRootKeyParams, SNRootKey, CreateNewRootKey } from '@standardnotes/encryption'
|
||||
import * as Responses from '@standardnotes/responses'
|
||||
import { Subscription } from '@standardnotes/security'
|
||||
import * as Common from '@standardnotes/common'
|
||||
|
||||
import { RemoteSession, RawStorageValue } from './Sessions/Types'
|
||||
import { RawStorageValue } from './Sessions/Types'
|
||||
import { ShareToken } from './ShareToken'
|
||||
import { SNApiService } from '../Api/ApiService'
|
||||
import { DiskStorageService } from '../Storage/DiskStorageService'
|
||||
@@ -48,8 +60,6 @@ import { ChallengeService } from '../Challenge'
|
||||
import {
|
||||
ApiCallError,
|
||||
ErrorMessage,
|
||||
ErrorTag,
|
||||
HttpErrorResponseBody,
|
||||
HttpServiceInterface,
|
||||
UserApiServiceInterface,
|
||||
UserRegistrationResponseBody,
|
||||
@@ -76,7 +86,7 @@ export class SNSessionManager
|
||||
extends AbstractService<SessionEvent>
|
||||
implements SessionsClientInterface, InternalEventHandlerInterface
|
||||
{
|
||||
private user?: Responses.User
|
||||
private user?: User
|
||||
private isSessionRenewChallengePresented = false
|
||||
private session?: Session | LegacySession
|
||||
|
||||
@@ -120,7 +130,7 @@ export class SNSessionManager
|
||||
super.deinit()
|
||||
}
|
||||
|
||||
private setUser(user?: Responses.User) {
|
||||
private setUser(user?: User) {
|
||||
this.user = user
|
||||
this.apiService.setUser(user)
|
||||
}
|
||||
@@ -167,10 +177,10 @@ export class SNSessionManager
|
||||
}
|
||||
|
||||
public offline() {
|
||||
return isNullOrUndefined(this.apiService.getSession())
|
||||
return this.apiService.getSession() == undefined
|
||||
}
|
||||
|
||||
public getUser(): Responses.User | undefined {
|
||||
public getUser(): User | undefined {
|
||||
return this.user
|
||||
}
|
||||
|
||||
@@ -187,7 +197,7 @@ export class SNSessionManager
|
||||
}
|
||||
|
||||
public getSureUser() {
|
||||
return this.user as Responses.User
|
||||
return this.user as User
|
||||
}
|
||||
|
||||
public getSession() {
|
||||
@@ -213,7 +223,7 @@ export class SNSessionManager
|
||||
|
||||
public async reauthenticateInvalidSession(
|
||||
cancelable = true,
|
||||
onResponse?: (response: Responses.HttpResponse) => void,
|
||||
onResponse?: (response: HttpResponse) => void,
|
||||
): Promise<void> {
|
||||
if (this.isSessionRenewChallengePresented) {
|
||||
return
|
||||
@@ -241,16 +251,16 @@ export class SNSessionManager
|
||||
const email = challengeResponse.values[0].value as string
|
||||
const password = challengeResponse.values[1].value as string
|
||||
const currentKeyParams = this.protocolService.getAccountKeyParams()
|
||||
const signInResult = await this.signIn(
|
||||
const { response } = await this.signIn(
|
||||
email,
|
||||
password,
|
||||
false,
|
||||
this.diskStorageService.isEphemeralSession(),
|
||||
currentKeyParams?.version,
|
||||
)
|
||||
if (signInResult.response.error) {
|
||||
if (isErrorResponse(response)) {
|
||||
this.challengeService.setValidationStatusForChallenge(challenge, challengeResponse!.values[1], false)
|
||||
onResponse?.(signInResult.response)
|
||||
onResponse?.(response)
|
||||
} else {
|
||||
resolve()
|
||||
this.challengeService.completeChallenge(challenge)
|
||||
@@ -263,26 +273,26 @@ export class SNSessionManager
|
||||
})
|
||||
}
|
||||
|
||||
public async getSubscription(): Promise<ClientDisplayableError | Subscription> {
|
||||
public async getSubscription(): Promise<ClientDisplayableError | Subscription | undefined> {
|
||||
const result = await this.apiService.getSubscription(this.getSureUser().uuid)
|
||||
|
||||
if (result.error) {
|
||||
return ClientDisplayableError.FromError(result.error)
|
||||
if (isErrorResponse(result)) {
|
||||
return ClientDisplayableError.FromError(result.data?.error)
|
||||
}
|
||||
|
||||
const subscription = (result as Responses.GetSubscriptionResponse).data!.subscription!
|
||||
const subscription = result.data.subscription
|
||||
|
||||
return subscription
|
||||
}
|
||||
|
||||
public async getAvailableSubscriptions(): Promise<Responses.AvailableSubscriptions | ClientDisplayableError> {
|
||||
public async getAvailableSubscriptions(): Promise<AvailableSubscriptions | ClientDisplayableError> {
|
||||
const response = await this.apiService.getAvailableSubscriptions()
|
||||
|
||||
if (response.error) {
|
||||
return ClientDisplayableError.FromError(response.error)
|
||||
if (isErrorResponse(response)) {
|
||||
return ClientDisplayableError.FromError(response.data.error)
|
||||
}
|
||||
|
||||
return (response as Responses.GetAvailableSubscriptionsResponse).data!
|
||||
return response.data
|
||||
}
|
||||
|
||||
private async promptForU2FVerification(username: string): Promise<Record<string, unknown> | undefined> {
|
||||
@@ -361,7 +371,7 @@ export class SNSessionManager
|
||||
const registerResponse = await this.userApiService.register({ email, serverPassword, keyParams, ephemeral })
|
||||
|
||||
if ('error' in registerResponse.data) {
|
||||
throw new ApiCallError((registerResponse.data as HttpErrorResponseBody).error.message)
|
||||
throw new ApiCallError(registerResponse.data.error.message)
|
||||
}
|
||||
|
||||
await this.handleAuthentication({
|
||||
@@ -376,37 +386,37 @@ export class SNSessionManager
|
||||
|
||||
private async retrieveKeyParams(dto: {
|
||||
email: string
|
||||
mfaKeyPath?: string
|
||||
mfaCode?: string
|
||||
authenticatorResponse?: Record<string, unknown>
|
||||
}): Promise<{
|
||||
keyParams?: SNRootKeyParams
|
||||
response: Responses.KeyParamsResponse | Responses.HttpResponse
|
||||
mfaKeyPath?: string
|
||||
response: HttpResponse<KeyParamsResponse>
|
||||
mfaCode?: string
|
||||
}> {
|
||||
const response = await this.apiService.getAccountKeyParams(dto)
|
||||
|
||||
if (response.error || isNullOrUndefined(response.data)) {
|
||||
if (isErrorResponse(response) || !response.data) {
|
||||
if (dto.mfaCode) {
|
||||
await this.alertService.alert(SignInStrings.IncorrectMfa)
|
||||
}
|
||||
|
||||
if ([ErrorTag.U2FRequired, ErrorTag.MfaRequired].includes(response.error?.tag as ErrorTag)) {
|
||||
const isU2FRequired = response.error?.tag === ErrorTag.U2FRequired
|
||||
const error = isErrorResponse(response) ? response.data.error : undefined
|
||||
|
||||
if (response.data && [ErrorTag.U2FRequired, ErrorTag.MfaRequired].includes(error?.tag as ErrorTag)) {
|
||||
const isU2FRequired = error?.tag === ErrorTag.U2FRequired
|
||||
const result = isU2FRequired ? await this.promptForU2FVerification(dto.email) : await this.promptForMfaValue()
|
||||
if (!result) {
|
||||
return {
|
||||
response: this.apiService.createErrorResponse(
|
||||
SignInStrings.SignInCanceledMissingMfa,
|
||||
Responses.StatusCode.CanceledMfa,
|
||||
undefined,
|
||||
ErrorTag.ClientCanceledMfa,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
return this.retrieveKeyParams({
|
||||
email: dto.email,
|
||||
mfaKeyPath: isU2FRequired ? undefined : response.error?.payload?.mfa_key,
|
||||
mfaCode: isU2FRequired ? undefined : (result as string),
|
||||
authenticatorResponse: isU2FRequired ? (result as Record<string, unknown>) : undefined,
|
||||
})
|
||||
@@ -415,13 +425,13 @@ export class SNSessionManager
|
||||
}
|
||||
}
|
||||
/** Make sure to use client value for identifier/email */
|
||||
const keyParams = KeyParamsFromApiResponse(response as Responses.KeyParamsResponse, dto.email)
|
||||
const keyParams = KeyParamsFromApiResponse(response.data, dto.email)
|
||||
if (!keyParams || !keyParams.version) {
|
||||
return {
|
||||
response: this.apiService.createErrorResponse(API_MESSAGE_FALLBACK_LOGIN_FAIL),
|
||||
}
|
||||
}
|
||||
return { keyParams, response, mfaKeyPath: dto.mfaKeyPath, mfaCode: dto.mfaCode }
|
||||
return { keyParams, response, mfaCode: dto.mfaCode }
|
||||
}
|
||||
|
||||
public async signIn(
|
||||
@@ -433,9 +443,9 @@ export class SNSessionManager
|
||||
): Promise<SessionManagerResponse> {
|
||||
const result = await this.performSignIn(email, password, strict, ephemeral, minAllowedVersion)
|
||||
if (
|
||||
result.response.error &&
|
||||
result.response.error.status !== Responses.StatusCode.LocalValidationError &&
|
||||
result.response.error.status !== Responses.StatusCode.CanceledMfa
|
||||
isErrorResponse(result.response) &&
|
||||
result.response.data.error.tag !== ErrorTag.ClientValidationError &&
|
||||
result.response.data.error.tag !== ErrorTag.ClientCanceledMfa
|
||||
) {
|
||||
const cleanedEmail = cleanedEmailString(email)
|
||||
if (cleanedEmail !== email) {
|
||||
@@ -461,7 +471,7 @@ export class SNSessionManager
|
||||
const paramsResult = await this.retrieveKeyParams({
|
||||
email,
|
||||
})
|
||||
if (paramsResult.response.error) {
|
||||
if (isErrorResponse(paramsResult.response)) {
|
||||
return {
|
||||
response: paramsResult.response,
|
||||
}
|
||||
@@ -512,7 +522,7 @@ export class SNSessionManager
|
||||
minAllowedVersion = this.protocolService.getLatestVersion()
|
||||
}
|
||||
|
||||
if (!isNullOrUndefined(minAllowedVersion)) {
|
||||
if (minAllowedVersion != undefined) {
|
||||
if (!Common.leftVersionGreaterThanOrEqualToRight(keyParams.version, minAllowedVersion)) {
|
||||
return {
|
||||
response: this.apiService.createErrorResponse(StrictSignInFailed(keyParams.version, minAllowedVersion)),
|
||||
@@ -521,6 +531,7 @@ export class SNSessionManager
|
||||
}
|
||||
const rootKey = await this.protocolService.computeRootKey(password, keyParams)
|
||||
const signInResponse = await this.bypassChecksAndSignInWithRootKey(email, rootKey, ephemeral)
|
||||
|
||||
return {
|
||||
response: signInResponse,
|
||||
}
|
||||
@@ -530,13 +541,14 @@ export class SNSessionManager
|
||||
email: string,
|
||||
rootKey: SNRootKey,
|
||||
ephemeral = false,
|
||||
): Promise<Responses.SignInResponse | Responses.HttpResponse> {
|
||||
): Promise<HttpResponse<SignInResponse>> {
|
||||
const { wrappingKey, canceled } = await this.challengeService.getWrappingKeyIfApplicable()
|
||||
|
||||
if (canceled) {
|
||||
return this.apiService.createErrorResponse(
|
||||
SignInStrings.PasscodeRequired,
|
||||
Responses.StatusCode.LocalValidationError,
|
||||
undefined,
|
||||
ErrorTag.ClientValidationError,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -546,18 +558,18 @@ export class SNSessionManager
|
||||
ephemeral,
|
||||
})
|
||||
|
||||
if (signInResponse.error || !signInResponse.data) {
|
||||
if (!signInResponse.data || isErrorResponse(signInResponse)) {
|
||||
return signInResponse
|
||||
}
|
||||
|
||||
const updatedKeyParams = (signInResponse as Responses.SignInResponse).data.key_params
|
||||
const updatedKeyParams = signInResponse.data.key_params
|
||||
const expandedRootKey = new SNRootKey(
|
||||
CopyPayloadWithContentOverride(rootKey.payload, {
|
||||
keyParams: updatedKeyParams || rootKey.keyParams.getPortableValue(),
|
||||
}),
|
||||
)
|
||||
|
||||
await this.handleSuccessAuthResponse(signInResponse as Responses.SignInResponse, expandedRootKey, wrappingKey)
|
||||
await this.handleSuccessAuthResponse(signInResponse, expandedRootKey, wrappingKey)
|
||||
|
||||
return signInResponse
|
||||
}
|
||||
@@ -577,59 +589,54 @@ export class SNSessionManager
|
||||
newEmail: parameters.newEmail,
|
||||
})
|
||||
|
||||
return this.processChangeCredentialsResponse(
|
||||
response as Responses.ChangeCredentialsResponse,
|
||||
parameters.newRootKey,
|
||||
parameters.wrappingKey,
|
||||
)
|
||||
return this.processChangeCredentialsResponse(response, parameters.newRootKey, parameters.wrappingKey)
|
||||
}
|
||||
|
||||
public async getSessionsList(): Promise<
|
||||
(Responses.HttpResponse & { data: RemoteSession[] }) | Responses.HttpResponse
|
||||
> {
|
||||
public async getSessionsList(): Promise<HttpResponse<SessionListEntry[]>> {
|
||||
const response = await this.apiService.getSessionsList()
|
||||
if (response.error || isNullOrUndefined(response.data)) {
|
||||
|
||||
if (isErrorResponse(response)) {
|
||||
return response
|
||||
}
|
||||
;(
|
||||
response as Responses.HttpResponse & {
|
||||
data: RemoteSession[]
|
||||
}
|
||||
).data = (response as Responses.SessionListResponse).data
|
||||
.map<RemoteSession>((session) => ({
|
||||
...session,
|
||||
updated_at: new Date(session.updated_at),
|
||||
}))
|
||||
.sort((s1: RemoteSession, s2: RemoteSession) => (s1.updated_at < s2.updated_at ? 1 : -1))
|
||||
|
||||
response.data = response.data.sort((s1: SessionListEntry, s2: SessionListEntry) => {
|
||||
return new Date(s1.updated_at) < new Date(s2.updated_at) ? 1 : -1
|
||||
})
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
public async revokeSession(sessionId: UuidString): Promise<Responses.HttpResponse> {
|
||||
const response = await this.apiService.deleteSession(sessionId)
|
||||
return response
|
||||
public async revokeSession(sessionId: UuidString): Promise<HttpResponse<SessionListResponse>> {
|
||||
return this.apiService.deleteSession(sessionId)
|
||||
}
|
||||
|
||||
public async revokeAllOtherSessions(): Promise<void> {
|
||||
const response = await this.getSessionsList()
|
||||
if (response.error != undefined || response.data == undefined) {
|
||||
throw new Error(response.error?.message ?? API_MESSAGE_GENERIC_SYNC_FAIL)
|
||||
if (isErrorResponse(response) || !response.data) {
|
||||
const error = isErrorResponse(response) ? response.data?.error : undefined
|
||||
throw new Error(error?.message ?? API_MESSAGE_GENERIC_SYNC_FAIL)
|
||||
}
|
||||
const sessions = response.data as RemoteSession[]
|
||||
const otherSessions = sessions.filter((session) => !session.current)
|
||||
|
||||
const otherSessions = response.data.filter((session) => !session.current)
|
||||
await Promise.all(otherSessions.map((session) => this.revokeSession(session.uuid)))
|
||||
}
|
||||
|
||||
private async processChangeCredentialsResponse(
|
||||
response: Responses.ChangeCredentialsResponse,
|
||||
response: HttpResponse<ChangeCredentialsResponse>,
|
||||
newRootKey: SNRootKey,
|
||||
wrappingKey?: SNRootKey,
|
||||
): Promise<SessionManagerResponse> {
|
||||
if (!response.error && response.data) {
|
||||
await this.handleSuccessAuthResponse(response as Responses.ChangeCredentialsResponse, newRootKey, wrappingKey)
|
||||
if (isErrorResponse(response)) {
|
||||
return {
|
||||
response: response,
|
||||
}
|
||||
}
|
||||
|
||||
await this.handleSuccessAuthResponse(response, newRootKey, wrappingKey)
|
||||
|
||||
return {
|
||||
response: response,
|
||||
keyParams: (response as Responses.ChangeCredentialsResponse).data?.key_params,
|
||||
keyParams: response.data?.key_params,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -690,7 +697,7 @@ export class SNSessionManager
|
||||
|
||||
private async populateSession(
|
||||
rootKey: SNRootKey,
|
||||
user: Responses.User,
|
||||
user: User,
|
||||
session: Session | LegacySession,
|
||||
host: string,
|
||||
wrappingKey?: SNRootKey,
|
||||
@@ -734,17 +741,17 @@ export class SNSessionManager
|
||||
* @deprecated use handleAuthentication instead
|
||||
*/
|
||||
private async handleSuccessAuthResponse(
|
||||
response: Responses.SignInResponse | Responses.ChangeCredentialsResponse,
|
||||
response: HttpSuccessResponse<SignInResponse | ChangeCredentialsResponse>,
|
||||
rootKey: SNRootKey,
|
||||
wrappingKey?: SNRootKey,
|
||||
) {
|
||||
const { data } = response
|
||||
const user = data.user as Responses.User
|
||||
const user = data.user
|
||||
|
||||
const isLegacyJwtResponse = data.token != undefined
|
||||
if (isLegacyJwtResponse) {
|
||||
const sessionOrError = LegacySession.create(data.token as string)
|
||||
if (!sessionOrError.isFailed()) {
|
||||
if (!sessionOrError.isFailed() && user) {
|
||||
await this.populateSession(rootKey, user, sessionOrError.getValue(), this.apiService.getHost(), wrappingKey)
|
||||
}
|
||||
} else if (data.session) {
|
||||
@@ -755,7 +762,7 @@ export class SNSessionManager
|
||||
data.session.refresh_expiration,
|
||||
data.session.readonly_access,
|
||||
)
|
||||
if (session !== null) {
|
||||
if (session !== null && user) {
|
||||
await this.populateSession(rootKey, user, session, this.apiService.getHost(), wrappingKey)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,3 @@ export type RawSessionPayload = {
|
||||
}
|
||||
|
||||
export type RawStorageValue = RawJwtPayload | RawSessionPayload
|
||||
|
||||
export type RemoteSession = {
|
||||
uuid: string
|
||||
updated_at: Date
|
||||
device_info: string
|
||||
current: boolean
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { SettingsList } from './SettingsList'
|
||||
import { SettingName, SensitiveSettingName, SubscriptionSettingName } from '@standardnotes/settings'
|
||||
import { API_MESSAGE_INVALID_SESSION } from '@standardnotes/services'
|
||||
import { StatusCode, User } from '@standardnotes/responses'
|
||||
import { HttpStatusCode, isErrorResponse, User } from '@standardnotes/responses'
|
||||
import { SettingsServerInterface } from './SettingsServerInterface'
|
||||
|
||||
/**
|
||||
@@ -31,30 +31,29 @@ export class SettingsGateway {
|
||||
}
|
||||
|
||||
async listSettings() {
|
||||
const { error, data } = await this.settingsApi.listSettings(this.userUuid)
|
||||
const response = await this.settingsApi.listSettings(this.userUuid)
|
||||
|
||||
if (error != undefined) {
|
||||
throw new Error(error.message)
|
||||
if (isErrorResponse(response)) {
|
||||
throw new Error(response.data?.error.message)
|
||||
}
|
||||
|
||||
if (data == undefined || data.settings == undefined) {
|
||||
if (response.data == undefined || response.data.settings == undefined) {
|
||||
return new SettingsList([])
|
||||
}
|
||||
|
||||
const settings: SettingsList = new SettingsList(data.settings)
|
||||
const settings: SettingsList = new SettingsList(response.data.settings)
|
||||
return settings
|
||||
}
|
||||
|
||||
async getSetting(name: SettingName): Promise<string | undefined> {
|
||||
const response = await this.settingsApi.getSetting(this.userUuid, name)
|
||||
|
||||
// Backend responds with 400 when setting doesn't exist
|
||||
if (response.status === StatusCode.HttpBadRequest) {
|
||||
if (response.status === HttpStatusCode.BadRequest) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (response.error != undefined) {
|
||||
throw new Error(response.error.message)
|
||||
if (isErrorResponse(response)) {
|
||||
throw new Error(response.data?.error.message)
|
||||
}
|
||||
|
||||
return response?.data?.setting?.value ?? undefined
|
||||
@@ -63,12 +62,12 @@ export class SettingsGateway {
|
||||
async getSubscriptionSetting(name: SubscriptionSettingName): Promise<string | undefined> {
|
||||
const response = await this.settingsApi.getSubscriptionSetting(this.userUuid, name)
|
||||
|
||||
if (response.status === StatusCode.HttpBadRequest) {
|
||||
if (response.status === HttpStatusCode.BadRequest) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (response.error != undefined) {
|
||||
throw new Error(response.error.message)
|
||||
if (isErrorResponse(response)) {
|
||||
throw new Error(response.data?.error.message)
|
||||
}
|
||||
|
||||
return response?.data?.setting?.value ?? undefined
|
||||
@@ -77,29 +76,28 @@ export class SettingsGateway {
|
||||
async getDoesSensitiveSettingExist(name: SensitiveSettingName): Promise<boolean> {
|
||||
const response = await this.settingsApi.getSetting(this.userUuid, name)
|
||||
|
||||
// Backend responds with 400 when setting doesn't exist
|
||||
if (response.status === StatusCode.HttpBadRequest) {
|
||||
if (response.status === HttpStatusCode.BadRequest) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (response.error != undefined) {
|
||||
throw new Error(response.error.message)
|
||||
if (isErrorResponse(response)) {
|
||||
throw new Error(response.data?.error.message)
|
||||
}
|
||||
|
||||
return response.data?.success ?? false
|
||||
}
|
||||
|
||||
async updateSetting(name: SettingName, payload: string, sensitive: boolean): Promise<void> {
|
||||
const { error } = await this.settingsApi.updateSetting(this.userUuid, name, payload, sensitive)
|
||||
if (error != undefined) {
|
||||
throw new Error(error.message)
|
||||
const response = await this.settingsApi.updateSetting(this.userUuid, name, payload, sensitive)
|
||||
if (isErrorResponse(response)) {
|
||||
throw new Error(response.data?.error.message)
|
||||
}
|
||||
}
|
||||
|
||||
async deleteSetting(name: SettingName): Promise<void> {
|
||||
const { error } = await this.settingsApi.deleteSetting(this.userUuid, name)
|
||||
if (error != undefined) {
|
||||
throw new Error(error.message)
|
||||
const response = await this.settingsApi.deleteSetting(this.userUuid, name)
|
||||
if (isErrorResponse(response)) {
|
||||
throw new Error(response.data?.error.message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
import {
|
||||
DeleteSettingResponse,
|
||||
GetSettingResponse,
|
||||
HttpResponse,
|
||||
ListSettingsResponse,
|
||||
UpdateSettingResponse,
|
||||
} from '@standardnotes/responses'
|
||||
import { UuidString } from '@Lib/Types/UuidString'
|
||||
|
||||
export interface SettingsServerInterface {
|
||||
listSettings(userUuid: UuidString): Promise<ListSettingsResponse>
|
||||
listSettings(userUuid: UuidString): Promise<HttpResponse<ListSettingsResponse>>
|
||||
|
||||
updateSetting(
|
||||
userUuid: UuidString,
|
||||
settingName: string,
|
||||
settingValue: string,
|
||||
sensitive: boolean,
|
||||
): Promise<UpdateSettingResponse>
|
||||
): Promise<HttpResponse<UpdateSettingResponse>>
|
||||
|
||||
getSetting(userUuid: UuidString, settingName: string): Promise<GetSettingResponse>
|
||||
getSetting(userUuid: UuidString, settingName: string): Promise<HttpResponse<GetSettingResponse>>
|
||||
|
||||
getSubscriptionSetting(userUuid: UuidString, settingName: string): Promise<GetSettingResponse>
|
||||
getSubscriptionSetting(userUuid: UuidString, settingName: string): Promise<HttpResponse<GetSettingResponse>>
|
||||
|
||||
deleteSetting(userUuid: UuidString, settingName: string): Promise<DeleteSettingResponse>
|
||||
deleteSetting(userUuid: UuidString, settingName: string): Promise<HttpResponse<DeleteSettingResponse>>
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import { arrayByDifference, nonSecureRandomIdentifier, subtractFromArray } from
|
||||
import { ServerSyncResponse } from '@Lib/Services/Sync/Account/Response'
|
||||
import { ResponseSignalReceiver, SyncSignal } from '@Lib/Services/Sync/Signals'
|
||||
import { SNApiService } from '../../Api/ApiService'
|
||||
import { RawSyncResponse } from '@standardnotes/responses'
|
||||
|
||||
export const SyncUpDownLimit = 150
|
||||
|
||||
@@ -56,12 +55,7 @@ export class AccountSyncOperation {
|
||||
})
|
||||
const payloads = this.popPayloads(this.upLimit)
|
||||
|
||||
const rawResponse = (await this.apiService.sync(
|
||||
payloads,
|
||||
this.lastSyncToken,
|
||||
this.paginationToken,
|
||||
this.downLimit,
|
||||
)) as RawSyncResponse
|
||||
const rawResponse = await this.apiService.sync(payloads, this.lastSyncToken, this.paginationToken, this.downLimit)
|
||||
|
||||
const response = new ServerSyncResponse(rawResponse)
|
||||
this.responses.push(response)
|
||||
|
||||
@@ -2,7 +2,9 @@ import {
|
||||
ApiEndpointParam,
|
||||
ConflictParams,
|
||||
ConflictType,
|
||||
Error,
|
||||
HttpError,
|
||||
HttpResponse,
|
||||
isErrorResponse,
|
||||
RawSyncResponse,
|
||||
ServerItemResponse,
|
||||
} from '@standardnotes/responses'
|
||||
@@ -12,24 +14,31 @@ import {
|
||||
ServerSyncSavedContextualPayload,
|
||||
FilteredServerItem,
|
||||
} from '@standardnotes/models'
|
||||
import { deepFreeze, isNullOrUndefined } from '@standardnotes/utils'
|
||||
import { deepFreeze } from '@standardnotes/utils'
|
||||
|
||||
export class ServerSyncResponse {
|
||||
public readonly rawResponse: RawSyncResponse
|
||||
public readonly savedPayloads: ServerSyncSavedContextualPayload[]
|
||||
public readonly retrievedPayloads: FilteredServerItem[]
|
||||
public readonly uuidConflictPayloads: FilteredServerItem[]
|
||||
public readonly dataConflictPayloads: FilteredServerItem[]
|
||||
public readonly rejectedPayloads: FilteredServerItem[]
|
||||
|
||||
constructor(rawResponse: RawSyncResponse) {
|
||||
private successResponseData: RawSyncResponse | undefined
|
||||
|
||||
constructor(public rawResponse: HttpResponse<RawSyncResponse>) {
|
||||
this.rawResponse = rawResponse
|
||||
|
||||
this.savedPayloads = FilterDisallowedRemotePayloadsAndMap(rawResponse.data?.saved_items || []).map((rawItem) => {
|
||||
return CreateServerSyncSavedPayload(rawItem)
|
||||
})
|
||||
if (!isErrorResponse(rawResponse)) {
|
||||
this.successResponseData = rawResponse.data
|
||||
}
|
||||
|
||||
this.retrievedPayloads = FilterDisallowedRemotePayloadsAndMap(rawResponse.data?.retrieved_items || [])
|
||||
this.savedPayloads = FilterDisallowedRemotePayloadsAndMap(this.successResponseData?.saved_items || []).map(
|
||||
(rawItem) => {
|
||||
return CreateServerSyncSavedPayload(rawItem)
|
||||
},
|
||||
)
|
||||
|
||||
this.retrievedPayloads = FilterDisallowedRemotePayloadsAndMap(this.successResponseData?.retrieved_items || [])
|
||||
|
||||
this.dataConflictPayloads = FilterDisallowedRemotePayloadsAndMap(this.rawDataConflictItems)
|
||||
|
||||
@@ -40,8 +49,8 @@ export class ServerSyncResponse {
|
||||
deepFreeze(this)
|
||||
}
|
||||
|
||||
public get error(): Error | undefined {
|
||||
return this.rawResponse.error || this.rawResponse.data?.error
|
||||
public get error(): HttpError | undefined {
|
||||
return isErrorResponse(this.rawResponse) ? this.rawResponse.data?.error : undefined
|
||||
}
|
||||
|
||||
public get status(): number {
|
||||
@@ -49,11 +58,11 @@ export class ServerSyncResponse {
|
||||
}
|
||||
|
||||
public get lastSyncToken(): string | undefined {
|
||||
return this.rawResponse.data?.[ApiEndpointParam.LastSyncToken]
|
||||
return this.successResponseData?.[ApiEndpointParam.LastSyncToken]
|
||||
}
|
||||
|
||||
public get paginationToken(): string | undefined {
|
||||
return this.rawResponse.data?.[ApiEndpointParam.PaginationToken]
|
||||
return this.successResponseData?.[ApiEndpointParam.PaginationToken]
|
||||
}
|
||||
|
||||
public get numberOfItemsInvolved(): number {
|
||||
@@ -75,7 +84,7 @@ export class ServerSyncResponse {
|
||||
return conflict.type === ConflictType.UuidConflict
|
||||
})
|
||||
.map((conflict) => {
|
||||
return conflict.unsaved_item || (conflict.item as ServerItemResponse)
|
||||
return conflict.unsaved_item || conflict.item!
|
||||
})
|
||||
}
|
||||
|
||||
@@ -85,7 +94,7 @@ export class ServerSyncResponse {
|
||||
return conflict.type === ConflictType.ConflictingData
|
||||
})
|
||||
.map((conflict) => {
|
||||
return conflict.server_item || (conflict.item as ServerItemResponse)
|
||||
return conflict.server_item || conflict.item!
|
||||
})
|
||||
}
|
||||
|
||||
@@ -99,17 +108,17 @@ export class ServerSyncResponse {
|
||||
)
|
||||
})
|
||||
.map((conflict) => {
|
||||
return conflict.unsaved_item as ServerItemResponse
|
||||
return conflict.unsaved_item!
|
||||
})
|
||||
}
|
||||
|
||||
private get rawConflictObjects(): ConflictParams[] {
|
||||
const conflicts = this.rawResponse.data?.conflicts || []
|
||||
const legacyConflicts = this.rawResponse.data?.unsaved || []
|
||||
const conflicts = this.successResponseData?.conflicts || []
|
||||
const legacyConflicts = this.successResponseData?.unsaved || []
|
||||
return conflicts.concat(legacyConflicts)
|
||||
}
|
||||
|
||||
public get hasError(): boolean {
|
||||
return !isNullOrUndefined(this.rawResponse.error)
|
||||
return this.error != undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,10 @@ export * from '@standardnotes/encryption'
|
||||
export * from '@standardnotes/features'
|
||||
export * from '@standardnotes/files'
|
||||
export * from '@standardnotes/models'
|
||||
|
||||
export * from '@standardnotes/responses'
|
||||
export { ErrorTag } from '@standardnotes/responses'
|
||||
|
||||
export * from '@standardnotes/services'
|
||||
export * from '@standardnotes/settings'
|
||||
export * from '@standardnotes/utils'
|
||||
|
||||
@@ -68,7 +68,7 @@ describe('basic auth', function () {
|
||||
this.application = await Factory.signOutApplicationAndReturnNew(this.application)
|
||||
const response = await this.application.signIn(this.email, this.password, undefined, undefined, undefined, true)
|
||||
expect(response).to.be.ok
|
||||
expect(response.error).to.not.be.ok
|
||||
expect(response.data.error).to.not.be.ok
|
||||
expect(await this.application.protocolService.getRootKey()).to.be.ok
|
||||
}).timeout(20000)
|
||||
|
||||
@@ -78,7 +78,7 @@ describe('basic auth', function () {
|
||||
this.application = await Factory.signOutApplicationAndReturnNew(this.application)
|
||||
const response = await this.application.signIn(this.email, this.password, undefined, undefined, undefined, true)
|
||||
expect(response).to.be.ok
|
||||
expect(response.error).to.not.be.ok
|
||||
expect(response.data.error).to.not.be.ok
|
||||
expect(await this.application.protocolService.getRootKey()).to.be.ok
|
||||
|
||||
let error
|
||||
@@ -110,7 +110,7 @@ describe('basic auth', function () {
|
||||
(async () => {
|
||||
const response = await this.application.signIn(this.email, this.password, undefined, undefined, undefined, true)
|
||||
expect(response).to.be.ok
|
||||
expect(response.error).to.not.be.ok
|
||||
expect(response.data.error).to.not.be.ok
|
||||
expect(await this.application.protocolService.getRootKey()).to.be.ok
|
||||
})(),
|
||||
(async () => {
|
||||
@@ -157,12 +157,12 @@ describe('basic auth', function () {
|
||||
|
||||
let response = await this.application.signIn(this.email, 'wrong password', undefined, undefined, undefined, true)
|
||||
expect(response).to.have.property('status', 401)
|
||||
expect(response.error).to.be.ok
|
||||
expect(response.data.error).to.be.ok
|
||||
|
||||
response = await this.application.signIn(this.email, this.password, undefined, undefined, undefined, true)
|
||||
|
||||
expect(response.status).to.equal(200)
|
||||
expect(response).to.not.haveOwnProperty('error')
|
||||
expect(response.data).to.not.haveOwnProperty('error')
|
||||
}).timeout(20000)
|
||||
|
||||
it('server retrieved key params should use our client inputted value for identifier', async function () {
|
||||
@@ -203,7 +203,7 @@ describe('basic auth', function () {
|
||||
this.application = await Factory.signOutApplicationAndReturnNew(this.application)
|
||||
const response = await this.application.signIn(uppercase, this.password, undefined, undefined, undefined, true)
|
||||
expect(response).to.be.ok
|
||||
expect(response.error).to.not.be.ok
|
||||
expect(response.data.error).to.not.be.ok
|
||||
expect(await this.application.protocolService.getRootKey()).to.be.ok
|
||||
}).timeout(20000)
|
||||
|
||||
@@ -219,7 +219,7 @@ describe('basic auth', function () {
|
||||
this.application = await Factory.signOutApplicationAndReturnNew(this.application)
|
||||
const response = await this.application.signIn(withspace, this.password, undefined, undefined, undefined, true)
|
||||
expect(response).to.be.ok
|
||||
expect(response.error).to.not.be.ok
|
||||
expect(response.data.error).to.not.be.ok
|
||||
expect(await this.application.protocolService.getRootKey()).to.be.ok
|
||||
}).timeout(20000)
|
||||
|
||||
@@ -228,7 +228,7 @@ describe('basic auth', function () {
|
||||
this.application = await Factory.signOutApplicationAndReturnNew(this.application)
|
||||
const response = await this.application.signIn(this.email, 'wrongpassword', undefined, undefined, undefined, true)
|
||||
expect(response).to.be.ok
|
||||
expect(response.error).to.be.ok
|
||||
expect(response.data.error).to.be.ok
|
||||
expect(await this.application.protocolService.getRootKey()).to.not.be.ok
|
||||
}).timeout(20000)
|
||||
|
||||
@@ -337,7 +337,7 @@ describe('basic auth', function () {
|
||||
const signinResponse = await this.application.signIn(this.email, newPassword, undefined, undefined, undefined, true)
|
||||
|
||||
expect(signinResponse).to.be.ok
|
||||
expect(signinResponse.error).to.not.be.ok
|
||||
expect(signinResponse.data.error).to.not.be.ok
|
||||
|
||||
expect(await this.application.protocolService.getRootKey()).to.be.ok
|
||||
|
||||
@@ -420,7 +420,7 @@ describe('basic auth', function () {
|
||||
)
|
||||
|
||||
expect(signinResponse).to.be.ok
|
||||
expect(signinResponse.error).to.not.be.ok
|
||||
expect(signinResponse.data.error).to.not.be.ok
|
||||
expect(await this.application.protocolService.getRootKey()).to.be.ok
|
||||
}
|
||||
}).timeout(80000)
|
||||
|
||||
@@ -172,8 +172,8 @@ describe('server session', function () {
|
||||
Factory.ignoreChallenges(this.application)
|
||||
const syncResponse = await this.application.apiService.sync([])
|
||||
expect(syncResponse.status).to.equal(401)
|
||||
expect(syncResponse.error.tag).to.equal('invalid-auth')
|
||||
expect(syncResponse.error.message).to.equal('Invalid login credentials.')
|
||||
expect(syncResponse.data.error.tag).to.equal('invalid-auth')
|
||||
expect(syncResponse.data.error.message).to.equal('Invalid login credentials.')
|
||||
})
|
||||
|
||||
it('sign out request should be performed successfully and terminate session with expired access token', async function () {
|
||||
@@ -194,8 +194,8 @@ describe('server session', function () {
|
||||
Factory.ignoreChallenges(this.application)
|
||||
const syncResponse = await this.application.apiService.sync([])
|
||||
expect(syncResponse.status).to.equal(401)
|
||||
expect(syncResponse.error.tag).to.equal('invalid-auth')
|
||||
expect(syncResponse.error.message).to.equal('Invalid login credentials.')
|
||||
expect(syncResponse.data.error.tag).to.equal('invalid-auth')
|
||||
expect(syncResponse.data.error.message).to.equal('Invalid login credentials.')
|
||||
})
|
||||
|
||||
it('change email request should be successful with a valid access token', async function () {
|
||||
@@ -207,8 +207,7 @@ describe('server session', function () {
|
||||
const newEmail = UuidGenerator.GenerateUuid()
|
||||
const changeEmailResponse = await application.changeEmail(newEmail, password)
|
||||
|
||||
expect(changeEmailResponse.status).to.equal(200)
|
||||
expect(changeEmailResponse.data.user).to.be.ok
|
||||
expect(changeEmailResponse.error).to.not.be.ok
|
||||
|
||||
application = await Factory.signOutApplicationAndReturnNew(application)
|
||||
const loginResponse = await Factory.loginToApplication({
|
||||
@@ -277,8 +276,7 @@ describe('server session', function () {
|
||||
|
||||
const changePasswordResponse = await this.application.changePassword(this.password, this.newPassword)
|
||||
|
||||
expect(changePasswordResponse.status).to.equal(200)
|
||||
expect(changePasswordResponse.data.user).to.be.ok
|
||||
expect(changePasswordResponse.error).to.not.be.ok
|
||||
|
||||
this.application = await Factory.signOutApplicationAndReturnNew(this.application)
|
||||
const loginResponse = await Factory.loginToApplication({
|
||||
@@ -305,8 +303,7 @@ describe('server session', function () {
|
||||
|
||||
const changePasswordResponse = await this.application.changePassword(this.password, this.newPassword)
|
||||
|
||||
expect(changePasswordResponse).to.be.ok
|
||||
expect(changePasswordResponse.status).to.equal(200)
|
||||
expect(changePasswordResponse.error).to.not.be.ok
|
||||
|
||||
this.application = await Factory.signOutApplicationAndReturnNew(this.application)
|
||||
const loginResponse = await Factory.loginToApplication({
|
||||
@@ -393,8 +390,8 @@ describe('server session', function () {
|
||||
const refreshSessionResponse = await this.application.apiService.refreshSession()
|
||||
|
||||
expect(refreshSessionResponse.status).to.equal(400)
|
||||
expect(refreshSessionResponse.error.tag).to.equal('expired-refresh-token')
|
||||
expect(refreshSessionResponse.error.message).to.equal('The refresh token has expired.')
|
||||
expect(refreshSessionResponse.data.error.tag).to.equal('expired-refresh-token')
|
||||
expect(refreshSessionResponse.data.error.message).to.equal('The refresh token has expired.')
|
||||
|
||||
/*
|
||||
The access token and refresh token should be expired up to this point.
|
||||
@@ -403,8 +400,8 @@ describe('server session', function () {
|
||||
Factory.ignoreChallenges(this.application)
|
||||
const syncResponse = await this.application.apiService.sync([])
|
||||
expect(syncResponse.status).to.equal(401)
|
||||
expect(syncResponse.error.tag).to.equal('invalid-auth')
|
||||
expect(syncResponse.error.message).to.equal('Invalid login credentials.')
|
||||
expect(syncResponse.data.error.tag).to.equal('invalid-auth')
|
||||
expect(syncResponse.data.error.message).to.equal('Invalid login credentials.')
|
||||
})
|
||||
|
||||
it('should fail when renewing a session with an invalid refresh token', async function () {
|
||||
@@ -428,8 +425,8 @@ describe('server session', function () {
|
||||
const refreshSessionResponse = await this.application.apiService.refreshSession()
|
||||
|
||||
expect(refreshSessionResponse.status).to.equal(400)
|
||||
expect(refreshSessionResponse.error.tag).to.equal('invalid-refresh-token')
|
||||
expect(refreshSessionResponse.error.message).to.equal('The refresh token is not valid.')
|
||||
expect(refreshSessionResponse.data.error.tag).to.equal('invalid-refresh-token')
|
||||
expect(refreshSessionResponse.data.error.message).to.equal('The refresh token is not valid.')
|
||||
|
||||
// Access token should remain valid.
|
||||
const syncResponse = await this.application.apiService.sync([])
|
||||
@@ -446,10 +443,10 @@ describe('server session', function () {
|
||||
const refreshPromise = this.application.apiService.refreshSession()
|
||||
const syncResponse = await this.application.apiService.sync([])
|
||||
|
||||
expect(syncResponse.error).to.be.ok
|
||||
expect(syncResponse.data.error).to.be.ok
|
||||
|
||||
const errorMessage = 'Your account session is being renewed with the server. Please try your request again.'
|
||||
expect(syncResponse.error.message).to.be.equal(errorMessage)
|
||||
expect(syncResponse.data.error.message).to.be.equal(errorMessage)
|
||||
/** Wait for finish so that test cleans up properly */
|
||||
await refreshPromise
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user