feat: dynamic authentication url and authentication using website token

This commit is contained in:
Michal Szczepanski 2023-10-11 07:43:53 +02:00
parent bc5414a27c
commit 06de9cbac9
35 changed files with 264 additions and 116 deletions

View File

@ -1,4 +1,4 @@
VERSION=1
WEB_URL=http://127.0.0.1:5173
WEB_URL=http://localhost:5173
IS_PRODUCTION=false
OBJ_LIST_LIMIT=20

View File

@ -65,10 +65,10 @@ export const environmentConfig: EnvironmentConfig = {
skipCssImageSizeMB: 2,
expertMode: false,
history: {
pinComment: true,
pinDraw: true,
pageComment: true,
pageNote: true
pinComment: false,
pinDraw: false,
pageComment: false,
pageNote: false
}
},
objListLimit: parseInt(process.env.OBJ_LIST_LIMIT || '100000')

View File

@ -50,6 +50,7 @@ export enum BusMessageType {
POPUP_IS_PDF = 'popup.is.pdf',
// Content script
CONTENT_DOWNLOAD_DATA = 'content.download',
CONTENT_EXTENSION_LOGIN = 'content.extension.login',
CONTENT_INVALIDATE = 'content.invalidate',
CONTENT_PING_URL = 'content.ping.url',
CONTENT_PIN_VISIBLE = 'content.pin.visible',

View File

@ -16,4 +16,5 @@
*/
export interface ObjServerDto {
id: number;
sub: string;
}

View File

@ -0,0 +1,30 @@
/*
* This file is part of the pinmenote-extension distribution (https://github.com/pinmenote/pinmenote-extension).
* Copyright (c) 2023 Michal Szczepanski.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { BrowserApi } from '@pinmenote/browser-api';
import { BusMessageType } from '../../../common/model/bus.model';
import { ICommand } from '../../../common/model/shared/common.dto';
import { TokenStorageGetCommand } from '../../../common/command/server/token/token-storage-get.command';
export class LoginExtensionCommand implements ICommand<Promise<void>> {
async execute(): Promise<void> {
const token = localStorage.getItem('accessToken');
const extensionToken = await new TokenStorageGetCommand().execute();
// we are logged in on website but not on extension
if (!extensionToken && token)
await BrowserApi.sendRuntimeMessage({ type: BusMessageType.CONTENT_EXTENSION_LOGIN, data: JSON.parse(token) });
}
}

View File

@ -34,6 +34,8 @@ import { RuntimePinGetHrefCommand } from './command/runtime/runtime-pin-get-href
import { TinyDispatcher } from '@pinmenote/tiny-dispatcher';
import { UrlFactory } from '../common/factory/url.factory';
import { fnUid } from '../common/fn/fn-uid';
import { environmentConfig } from '../common/environment';
import { LoginExtensionCommand } from './command/login/login-extension.command';
class PinMeScript {
private href: string;
@ -56,6 +58,8 @@ class PinMeScript {
private handlePinSettings = async (event: string, key: string): Promise<void> => {
TinyDispatcher.getInstance().removeListener(event, key);
if (location.origin === environmentConfig.defaultServer) await new LoginExtensionCommand().execute();
await ContentSettingsStore.initSettings();
await new RuntimePinGetHrefCommand().execute();

View File

@ -0,0 +1,27 @@
/*
* This file is part of the pinmenote-extension distribution (https://github.com/pinmenote/pinmenote-extension).
* Copyright (c) 2023 Michal Szczepanski.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { FetchService } from '@pinmenote/fetch-service';
import { ICommand } from '../../../common/model/shared/common.dto';
import { environmentConfig } from '../../../common/environment';
export class ApiAuthUrlCommand implements ICommand<Promise<string>> {
async execute(): Promise<string> {
const req = await FetchService.fetch<{ auth: string }>(`${environmentConfig.defaultServer}/api/server.json`, {});
if (!req.ok) throw new Error('Invalid api url');
return req.data.auth;
}
}

View File

@ -18,7 +18,6 @@ import { AccessTokenDto, TokenDataDto } from '../../../common/model/shared/token
import { FetchHeaders, RefreshTokenParams } from '@pinmenote/fetch-service';
import { TokenStorageGetCommand } from '../../../common/command/server/token/token-storage-get.command';
import { TokenStorageSetCommand } from '../../../common/command/server/token/token-storage-set.command';
import { environmentConfig } from '../../../common/environment';
import { fnConsoleLog } from '../../../common/fn/fn-console';
import jwtDecode from 'jwt-decode';
@ -26,10 +25,6 @@ export class ApiCallBase {
protected token: AccessTokenDto | undefined;
protected tokenData: TokenDataDto | undefined;
get apiUrl(): string {
return environmentConfig.defaultServer;
}
get storeUrl(): string | undefined {
return this.tokenData?.data.store;
}
@ -40,31 +35,29 @@ export class ApiCallBase {
this.tokenData = jwtDecode<TokenDataDto>(this.token.access_token);
}
protected refreshParams(): RefreshTokenParams {
protected refreshParams(baseUrl: string): RefreshTokenParams {
return {
data: {
refreshKey: 'message',
refreshValue: 'jwt expired',
method: 'PUT',
url: `${this.apiUrl}/api/v1/auth/refresh-token`
url: `${baseUrl}/api/v1/refresh-token`
},
successCallback: (res) => {
if (res.status === 200) {
const value: AccessTokenDto = JSON.parse(res.data);
new TokenStorageSetCommand(value)
.execute()
.then(() => {
/* IGNORE */
})
.catch(() => {
/* IGNORE */
});
return {
Authorization: `Bearer ${value.access_token}`
} as FetchHeaders;
}
successCallback: (res, headers) => {
fnConsoleLog('refreshParams->successCallback', res);
return {};
const value: AccessTokenDto = JSON.parse(res.data);
new TokenStorageSetCommand(value)
.execute()
.then(() => {
/* IGNORE */
})
.catch(() => {
/* IGNORE */
});
return {
...headers,
Authorization: `Bearer ${value.access_token}`
} as FetchHeaders;
},
errorCallback: (error) => {
fnConsoleLog('refreshParams->errorCallback', error);

View File

@ -20,6 +20,7 @@ import { ICommand, ServerErrorDto } from '../../../common/model/shared/common.dt
import { ApiCallBase } from './api-call.base';
import { apiResponseError } from './api.model';
import { fnConsoleLog } from '../../../common/fn/fn-console';
import { ApiAuthUrlCommand } from './api-auth-url.command';
export class ApiLoginCommand
extends ApiCallBase
@ -31,7 +32,8 @@ export class ApiLoginCommand
async execute(): Promise<FetchResponse<AccessTokenDto | ServerErrorDto>> {
fnConsoleLog('ApiLoginCommand->execute');
const url = `${this.apiUrl}/api/v1/auth/login`;
const baseUrl = await new ApiAuthUrlCommand().execute();
const url = `${baseUrl}/api/v1/login`;
try {
return await FetchService.fetch<AccessTokenDto | ServerErrorDto>(url, {
method: 'POST',

View File

@ -19,6 +19,7 @@ import { FetchResponse, FetchService } from '@pinmenote/fetch-service';
import { ApiCallBase } from './api-call.base';
import { fnConsoleLog } from '../../../common/fn/fn-console';
import { ApiErrorCode } from '../../../common/model/shared/api.error-code';
import { ApiAuthUrlCommand } from './api-auth-url.command';
export class ApiLogoutCommand
extends ApiCallBase
@ -26,7 +27,8 @@ export class ApiLogoutCommand
{
async execute(): Promise<FetchResponse<BoolDto | ServerErrorDto>> {
await this.initTokenData();
const url = `${this.apiUrl}/api/v1/auth/logout`;
const baseUrl = await new ApiAuthUrlCommand().execute();
const url = `${baseUrl}/api/v1/logout`;
fnConsoleLog('ApiLogoutCommand->execute', url);
try {
return await FetchService.fetch<BoolDto>(
@ -35,7 +37,7 @@ export class ApiLogoutCommand
method: 'POST',
headers: this.getAuthHeaders()
},
this.refreshParams()
this.refreshParams(baseUrl)
);
} catch (e) {
fnConsoleLog('ERROR', e);

View File

@ -20,6 +20,7 @@ import { ICommand, ServerErrorDto } from '../../../common/model/shared/common.dt
import { ApiCallBase } from './api-call.base';
import { fnConsoleLog } from '../../../common/fn/fn-console';
import { ApiErrorCode } from '../../../common/model/shared/api.error-code';
import { ApiAuthUrlCommand } from './api-auth-url.command';
export class ApiVerify2faCommand
extends ApiCallBase
@ -31,7 +32,8 @@ export class ApiVerify2faCommand
async execute(): Promise<FetchResponse<AccessTokenDto | ServerErrorDto>> {
fnConsoleLog('ApiVerify2faCommand->execute');
const url = `${this.apiUrl}/api/v1/auth/2fa/verify`;
const baseUrl = await new ApiAuthUrlCommand().execute();
const url = `${baseUrl}/api/v1/2fa/verify`;
try {
return await FetchService.fetch<AccessTokenDto>(url, {
method: 'POST',

View File

@ -21,6 +21,9 @@ import { ICommand } from '../../../../common/model/shared/common.dto';
import { fnConsoleLog } from '../../../../common/fn/fn-console';
export class ApiStoreBeginCommand extends ApiCallBase implements ICommand<Promise<BeginTxResponse | undefined>> {
constructor(private authUrl: string) {
super();
}
async execute(): Promise<BeginTxResponse | undefined> {
await this.initTokenData();
if (!this.storeUrl) return;
@ -28,7 +31,7 @@ export class ApiStoreBeginCommand extends ApiCallBase implements ICommand<Promis
const resp = await FetchService.fetch<BeginTxResponse>(
`${this.storeUrl}/api/v1/tx/begin`,
{ headers: this.getAuthHeaders() },
this.refreshParams()
this.refreshParams(this.authUrl)
);
return resp.data;
} catch (e) {

View File

@ -21,7 +21,7 @@ import { ICommand } from '../../../../common/model/shared/common.dto';
import { fnConsoleLog } from '../../../../common/fn/fn-console';
export class ApiStoreCommitCommand extends ApiCallBase implements ICommand<Promise<boolean>> {
constructor(private tx: BeginTxResponse) {
constructor(private authUrl: string, private tx: BeginTxResponse) {
super();
}
async execute(): Promise<boolean> {
@ -34,7 +34,7 @@ export class ApiStoreCommitCommand extends ApiCallBase implements ICommand<Promi
type: 'TEXT',
headers: this.getAuthHeaders()
},
this.refreshParams()
this.refreshParams(this.authUrl)
);
return resp.ok;
} catch (e) {

View File

@ -29,10 +29,11 @@ export interface ObjAddRequest {
export interface ObjAddResponse {
serverId: number;
sub: string;
}
export class ApiObjAddCommand extends ApiCallBase implements ICommand<Promise<ObjAddResponse | ServerErrorDto>> {
constructor(private obj: ObjDto, private hash: string, private tx: BeginTxResponse) {
constructor(private authUrl: string, private obj: ObjDto, private hash: string, private tx: BeginTxResponse) {
super();
}
async execute(): Promise<ObjAddResponse | ServerErrorDto> {
@ -49,7 +50,7 @@ export class ApiObjAddCommand extends ApiCallBase implements ICommand<Promise<Ob
hash: this.hash
})
},
this.refreshParams()
this.refreshParams(this.authUrl)
);
// fnConsoleLog('ApiStoreSyncInfoCommand->response', resp);
return resp.data;

View File

@ -17,26 +17,34 @@
import { ApiCallBase } from '../../api-call.base';
import { ICommand, ServerErrorDto } from '../../../../../common/model/shared/common.dto';
import { ApiErrorCode } from '../../../../../common/model/shared/api.error-code';
import { FetchService } from '@pinmenote/fetch-service';
import { BeginTxResponse, ObjSingleChange } from '../api-store.model';
import { ObjSingleChange } from '../api-store.model';
import { fnConsoleLog } from '../../../../../common/fn/fn-console';
export class ApiObjGetByHashCommand extends ApiCallBase implements ICommand<Promise<ObjSingleChange | ServerErrorDto>> {
constructor(private hash: string, private tx: BeginTxResponse) {
export interface ObjSingleChangeSub extends ObjSingleChange {
sub: string;
}
export class ApiObjGetByHashCommand extends ApiCallBase implements ICommand<Promise<ObjSingleChangeSub>> {
constructor(private authUrl: string, private hash: string) {
super();
}
async execute(): Promise<ObjSingleChange | ServerErrorDto> {
async execute(): Promise<ObjSingleChangeSub> {
await this.initTokenData();
if (!this.storeUrl) return { code: ApiErrorCode.INTERNAL, message: 'ApiStoreObjGetByHashCommand' };
if (!this.storeUrl) {
fnConsoleLog('ApiStoreObjGetByHashCommand', this.storeUrl);
throw new Error('PROBLEM !!!!!!!!!!!!!!!');
}
const resp = await FetchService.fetch<ObjSingleChange | ServerErrorDto>(
`${this.storeUrl}/api/v1/obj/hash/${this.hash}`,
{
headers: this.getAuthHeaders(),
method: 'GET'
},
this.refreshParams()
this.refreshParams(this.authUrl)
);
// fnConsoleLog('ApiStoreObjGetByHashCommand->response', resp);
return resp.data;
if (!resp.ok) throw new Error('PROBLEM !!!!!!!!!!!!!!!');
return { ...resp.data, sub: this.tokenData?.sub } as ObjSingleChangeSub;
}
}

View File

@ -27,7 +27,7 @@ export class ApiObjGetChangesCommand
extends ApiCallBase
implements ICommand<Promise<ObjChangesResponse | ServerErrorDto>>
{
constructor(private serverId: number) {
constructor(private authUrl: string, private serverId: number) {
super();
}
async execute(): Promise<ObjChangesResponse | ServerErrorDto> {
@ -37,7 +37,7 @@ export class ApiObjGetChangesCommand
const resp = await FetchService.fetch<ObjChangesResponse | ServerErrorDto>(
`${this.storeUrl}/api/v1/obj/changes?serverId=${this.serverId}`,
{ headers: this.getAuthHeaders() },
this.refreshParams()
this.refreshParams(this.authUrl)
);
if (resp.status === 200) return resp.data;
fnConsoleLog(resp);

View File

@ -21,7 +21,7 @@ import { ICommand } from '../../../../../common/model/shared/common.dto';
import { fnConsoleLog } from '../../../../../common/fn/fn-console';
export class ApiObjGetChangesCommand extends ApiCallBase implements ICommand<Promise<ObjSingleChange | undefined>> {
constructor(private id: number) {
constructor(private authUrl: string, private id: number) {
super();
}
async execute(): Promise<ObjSingleChange | undefined> {
@ -31,7 +31,7 @@ export class ApiObjGetChangesCommand extends ApiCallBase implements ICommand<Pro
const resp = await FetchService.fetch<ObjSingleChange>(
`${this.storeUrl}/api/v1/obj/${this.id}`,
{ headers: this.getAuthHeaders() },
this.refreshParams()
this.refreshParams(this.authUrl)
);
fnConsoleLog('ApiStoreChangesCommand->response', resp);
return resp.data;

View File

@ -30,13 +30,13 @@ export interface FileDataDto {
}
export class ApiSegmentAddCommand extends ApiCallBase implements ICommand<Promise<boolean>> {
constructor(private tx: BeginTxResponse, private file: string, private data: FileDataDto) {
constructor(private authUrl: string, private tx: BeginTxResponse, private file: string, private data: FileDataDto) {
super();
}
async execute(): Promise<boolean> {
await this.initTokenData();
if (!this.storeUrl) return false;
// if (await this.hasSegment(this.storeUrl)) return true;
if (await this.hasSegment(this.storeUrl)) return true;
return await this.addSegment(this.storeUrl);
}
@ -52,7 +52,7 @@ export class ApiSegmentAddCommand extends ApiCallBase implements ICommand<Promis
...authHeaders
}
},
this.refreshParams()
this.refreshParams(this.authUrl)
);
return resp.status === 200;
}
@ -99,7 +99,7 @@ export class ApiSegmentAddCommand extends ApiCallBase implements ICommand<Promis
method: 'POST',
body: formData
},
this.refreshParams()
this.refreshParams(this.authUrl)
);
// fnConsoleLog('ApiSegmentAddCommand->resp', resp);
return resp.status === 200;

View File

@ -24,7 +24,7 @@ export class ApiSegmentGetChildrenCommand
extends ApiCallBase
implements ICommand<Promise<SegmentHashListResponse | undefined>>
{
constructor(private hash: string) {
constructor(private authUrl: string, private hash: string) {
super();
}
async execute(): Promise<SegmentHashListResponse | undefined> {
@ -34,7 +34,7 @@ export class ApiSegmentGetChildrenCommand
const resp = await FetchService.fetch<SegmentHashListResponse>(
`${this.storeUrl}/api/v1/segment/children/${this.hash}`,
{ headers: this.getAuthHeaders() },
this.refreshParams()
this.refreshParams(this.authUrl)
);
return resp.data;
} catch (e) {

View File

@ -20,7 +20,7 @@ import { ICommand } from '../../../../../common/model/shared/common.dto';
import { fnConsoleLog } from '../../../../../common/fn/fn-console';
export class ApiSegmentGetCommand extends ApiCallBase implements ICommand<Promise<Blob | undefined>> {
constructor(private hash: string, private mimeType?: string) {
constructor(private authUrl: string, private hash: string, private mimeType?: string) {
super();
}
async execute(): Promise<Blob | undefined> {
@ -31,7 +31,7 @@ export class ApiSegmentGetCommand extends ApiCallBase implements ICommand<Promis
const resp = await FetchService.fetch<Blob>(
`${this.storeUrl}/api/v1/segment/${this.hash}${mimeType}`,
{ headers: this.getAuthHeaders(), type: 'BLOB' },
this.refreshParams()
this.refreshParams(this.authUrl)
);
return resp.data;
} catch (e) {

View File

@ -25,7 +25,7 @@ export class ApiSegmentQuotaGetCommand
extends ApiCallBase
implements ICommand<Promise<ServerQuotaResponse | ServerErrorDto>>
{
constructor() {
constructor(private authUrl: string) {
super();
}
async execute(): Promise<ServerQuotaResponse | ServerErrorDto> {
@ -38,7 +38,7 @@ export class ApiSegmentQuotaGetCommand
type: 'JSON',
headers: this.getAuthHeaders(true)
},
this.refreshParams()
this.refreshParams(this.authUrl)
);
return resp.data;
} catch (e) {

View File

@ -0,0 +1,68 @@
/*
* This file is part of the pinmenote-extension distribution (https://github.com/pinmenote/pinmenote-extension).
* Copyright (c) 2023 Michal Szczepanski.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { FetchService, RefreshTokenParams, FetchHeaders } from '@pinmenote/fetch-service';
import { ICommand, ServerErrorDto } from '../../../common/model/shared/common.dto';
import { AccessTokenDto } from '../../../common/model/shared/token.dto';
import { TokenStorageSetCommand } from '../../../common/command/server/token/token-storage-set.command';
import { fnConsoleLog } from '../../../common/fn/fn-console';
import { ApiAuthUrlCommand } from '../api/api-auth-url.command';
export class ContentExtensionLoginCommand implements ICommand<Promise<void>> {
constructor(private token: AccessTokenDto) {}
async execute(): Promise<void> {
fnConsoleLog('ContentExtensionLoginCommand->execute');
await new ApiAuthUrlCommand().execute();
const baseUrl = await new ApiAuthUrlCommand().execute();
const req = await FetchService.fetch<AccessTokenDto | ServerErrorDto>(
`${baseUrl}/api/v1/login/new-device`,
{
method: 'PATCH',
headers: {
Authorization: `Bearer ${this.token.access_token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ source: 'EXTENSION' })
},
this.refreshParams(baseUrl)
);
if (req.ok && 'access_token' in req.data) {
await new TokenStorageSetCommand(req.data).execute();
}
}
protected refreshParams(baseUrl: string): RefreshTokenParams {
return {
data: {
refreshKey: 'message',
refreshValue: 'jwt expired',
method: 'PUT',
url: `${baseUrl}/api/v1/refresh-token`
},
successCallback: (res, headers) => {
const value: AccessTokenDto = JSON.parse(res.data);
fnConsoleLog('ContentExtensionLoginCommand->successCallback', res, 'value', value, 'headers', headers);
return {
...headers,
Authorization: `Bearer ${value.access_token}`
} as FetchHeaders;
},
errorCallback: (error) => {
fnConsoleLog('ContentExtensionLoginCommand->errorCallback', error);
}
};
}
}

View File

@ -23,7 +23,7 @@ import { BeginTxResponse } from '../../api/store/api-store.model';
import { SyncObjectStatus } from '../../../../common/model/sync.model';
export class SyncNoteCommand implements ICommand<Promise<SyncObjectStatus>> {
constructor(private obj: ObjDto<ObjNoteDto>, private tx: BeginTxResponse) {}
constructor(private authUrl: string, private obj: ObjDto<ObjNoteDto>, private tx: BeginTxResponse) {}
// eslint-disable-next-line @typescript-eslint/require-await
async execute(): Promise<SyncObjectStatus> {
fnConsoleLog('SyncNoteCommand');

View File

@ -14,25 +14,29 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { BrowserStorage } from '@pinmenote/browser-api';
import { ICommand, ServerErrorDto } from '../../../../common/model/shared/common.dto';
import { ObjDto } from '../../../../common/model/obj/obj.dto';
import { fnConsoleLog } from '../../../../common/fn/fn-console';
import { ApiObjAddCommand, ObjAddResponse } from '../../api/store/obj/api-obj-add.command';
import { BeginTxResponse, ObjSingleChange } from '../../api/store/api-store.model';
import { ApiObjGetByHashCommand, ObjSingleChangeSub } from '../../api/store/obj/api-obj-get-by-hash.command';
import { ICommand, ServerErrorDto } from '../../../../common/model/shared/common.dto';
import { ApiErrorCode } from '../../../../common/model/shared/api.error-code';
import { ApiObjGetByHashCommand } from '../../api/store/obj/api-obj-get-by-hash.command';
import { BeginTxResponse } from '../../api/store/api-store.model';
import { BrowserStorage } from '@pinmenote/browser-api';
import { ObjDto } from '../../../../common/model/obj/obj.dto';
import { ObjectStoreKeys } from '../../../../common/keys/object.store.keys';
import { fnSleep } from '../../../../common/fn/fn-sleep';
import { fnConsoleLog } from '../../../../common/fn/fn-console';
export class SyncObjectCommand implements ICommand<Promise<void>> {
constructor(private obj: ObjDto, private hash: string, private tx: BeginTxResponse) {}
constructor(private authUrl: string, private obj: ObjDto, private hash: string, private tx: BeginTxResponse) {}
async execute(): Promise<void> {
if (this.obj.server?.id) return;
const resp: ObjAddResponse | ServerErrorDto = await new ApiObjAddCommand(this.obj, this.hash, this.tx).execute();
const resp: ObjAddResponse | ServerErrorDto = await new ApiObjAddCommand(
this.authUrl,
this.obj,
this.hash,
this.tx
).execute();
if ('serverId' in resp) {
return await this.saveServerId(resp.serverId);
return await this.saveServerId(resp.serverId, resp.sub);
} else if ('code' in resp && resp.code === ApiErrorCode.SYNC_DUPLICATED_HASH) {
return await this.setByHash();
}
@ -41,19 +45,12 @@ export class SyncObjectCommand implements ICommand<Promise<void>> {
}
private async setByHash(): Promise<void> {
const resp: ObjSingleChange | ServerErrorDto = await new ApiObjGetByHashCommand(this.hash, this.tx).execute();
if ('serverId' in resp) {
await this.saveServerId(resp.serverId);
return;
}
fnConsoleLog('SyncObjectCommand->setByHash');
throw new Error('PROBLEM !!!!!!!!!!!!!!!');
const resp: ObjSingleChangeSub = await new ApiObjGetByHashCommand(this.authUrl, this.hash).execute();
await this.saveServerId(resp.serverId, resp.sub);
}
private async saveServerId(serverId: number): Promise<void> {
this.obj.server = { id: serverId };
private async saveServerId(serverId: number, sub: string): Promise<void> {
this.obj.server = { id: serverId, sub };
await BrowserStorage.set(`${ObjectStoreKeys.OBJECT_ID}:${this.obj.id}`, this.obj);
await BrowserStorage.set(`${ObjectStoreKeys.SERVER_ID}:${serverId}`, this.obj.id);
await fnSleep(500);
fnConsoleLog('SyncObjectCommand->saveServerId', serverId, 'obj->id', this.obj.id);
}
}

View File

@ -27,11 +27,11 @@ import { ApiSegmentAddCommand } from '../../api/store/segment/api-segment-add.co
import { SyncCryptoFactory } from '../crypto/sync-crypto.factory';
export class SyncPageNoteCommand implements ICommand<Promise<SyncObjectStatus>> {
constructor(private obj: ObjDto<ObjPageNoteDto>, private tx: BeginTxResponse) {}
constructor(private authUrl: string, private obj: ObjDto<ObjPageNoteDto>, private tx: BeginTxResponse) {}
async execute(): Promise<SyncObjectStatus> {
const data = this.obj.data;
await new SyncObjectCommand(this.obj, data.hash, this.tx).execute();
await new SyncObjectCommand(this.authUrl, this.obj, data.hash, this.tx).execute();
await this.syncNote(data);
@ -40,7 +40,7 @@ export class SyncPageNoteCommand implements ICommand<Promise<SyncObjectStatus>>
private async syncNote(data: ObjPageNoteDto): Promise<void> {
const content = JSON.stringify(data);
await new ApiSegmentAddCommand(this.tx, content, {
await new ApiSegmentAddCommand(this.authUrl, this.tx, content, {
key: await SyncCryptoFactory.newKey(),
type: SyncHashType.ObjPdfDataDto,
hash: data.hash

View File

@ -26,7 +26,7 @@ import { ApiSegmentAddCommand } from '../../api/store/segment/api-segment-add.co
import { SyncCryptoFactory } from '../crypto/sync-crypto.factory';
export class SyncPdfCommand implements ICommand<Promise<SyncObjectStatus>> {
constructor(private obj: ObjDto<ObjPdfDto>, private tx: BeginTxResponse) {}
constructor(private authUrl: string, private obj: ObjDto<ObjPdfDto>, private tx: BeginTxResponse) {}
async execute(): Promise<SyncObjectStatus> {
const data = this.obj.data;
await new SyncObjectCommand(this.obj, data.hash, this.tx).execute();
@ -39,7 +39,7 @@ export class SyncPdfCommand implements ICommand<Promise<SyncObjectStatus>> {
private async syncData(data: ObjPdfDataDto, parent: string): Promise<void> {
const content = JSON.stringify(data);
await new ApiSegmentAddCommand(this.tx, content, {
await new ApiSegmentAddCommand(this.authUrl, this.tx, content, {
key: await SyncCryptoFactory.newKey(),
type: SyncHashType.ObjPdfDataDto,
hash: data.hash,
@ -50,7 +50,7 @@ export class SyncPdfCommand implements ICommand<Promise<SyncObjectStatus>> {
private async syncPdf(hash: string): Promise<void> {
const pdfData = await BrowserStorage.get<string | undefined>(`${ObjectStoreKeys.PDF_DATA}:${hash}`);
if (!pdfData) return;
await new ApiSegmentAddCommand(this.tx, pdfData, {
await new ApiSegmentAddCommand(this.authUrl, this.tx, pdfData, {
key: await SyncCryptoFactory.newKey(),
type: SyncHashType.ObjPdf,
hash

View File

@ -27,7 +27,7 @@ import { ObjVideoDataDto } from '../../../../common/model/obj/page-snapshot.dto'
import { SyncCryptoFactory } from '../crypto/sync-crypto.factory';
export class SyncPinCommand implements ICommand<Promise<SyncObjectStatus>> {
constructor(private obj: ObjDto<ObjPinDto>, private tx: BeginTxResponse) {}
constructor(private authUrl: string, private obj: ObjDto<ObjPinDto>, private tx: BeginTxResponse) {}
async execute(): Promise<SyncObjectStatus> {
const data = this.obj.data;
await new SyncObjectCommand(this.obj, data.data.hash, this.tx).execute();
@ -44,7 +44,7 @@ export class SyncPinCommand implements ICommand<Promise<SyncObjectStatus>> {
private syncPinVideo = async (parent: string, data?: ObjVideoDataDto) => {
if (!data) return;
const content = JSON.stringify(data);
await new ApiSegmentAddCommand(this.tx, content, {
await new ApiSegmentAddCommand(this.authUrl, this.tx, content, {
key: await SyncCryptoFactory.newKey(),
type: SyncHashType.ObjVideoDataDto,
hash: data.hash,
@ -56,7 +56,7 @@ export class SyncPinCommand implements ICommand<Promise<SyncObjectStatus>> {
for (const draw of data) {
// TODO SYNC DRAW LIKE COMMENTS
const content = JSON.stringify(draw);
await new ApiSegmentAddCommand(this.tx, content, {
await new ApiSegmentAddCommand(this.authUrl, this.tx, content, {
key: await SyncCryptoFactory.newKey(),
type: SyncHashType.ObjDrawDto,
hash: draw.hash,
@ -70,7 +70,7 @@ export class SyncPinCommand implements ICommand<Promise<SyncObjectStatus>> {
const comment = await new PinGetCommentCommand(hash).execute();
if (!comment) continue;
const content = JSON.stringify(comment);
await new ApiSegmentAddCommand(this.tx, content, {
await new ApiSegmentAddCommand(this.authUrl, this.tx, content, {
key: await SyncCryptoFactory.newKey(),
type: SyncHashType.ObjCommentDto,
hash: comment.hash,
@ -81,7 +81,7 @@ export class SyncPinCommand implements ICommand<Promise<SyncObjectStatus>> {
private syncPinData = async (data: ObjPinDataDto) => {
const content = JSON.stringify(data);
await new ApiSegmentAddCommand(this.tx, content, {
await new ApiSegmentAddCommand(this.authUrl, this.tx, content, {
key: await SyncCryptoFactory.newKey(),
type: SyncHashType.ObjPinDataDto,
hash: data.hash
@ -90,7 +90,7 @@ export class SyncPinCommand implements ICommand<Promise<SyncObjectStatus>> {
private syncPinDescription = async (data: ObjPinDescription, parent: string) => {
const content = JSON.stringify(data);
await new ApiSegmentAddCommand(this.tx, content, {
await new ApiSegmentAddCommand(this.authUrl, this.tx, content, {
key: await SyncCryptoFactory.newKey(),
type: SyncHashType.ObjPinDescription,
hash: data.hash,

View File

@ -14,14 +14,14 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { ObjDto, ObjRemovedDto } from '../../../../common/model/obj/obj.dto';
import { ObjRemovedDto } from '../../../../common/model/obj/obj.dto';
import { ICommand } from '../../../../common/model/shared/common.dto';
import { SyncObjectStatus } from '../../../../common/model/sync.model';
import { fnConsoleLog } from '../../../../common/fn/fn-console';
import { BeginTxResponse } from '../../api/store/api-store.model';
export class SyncRemovedCommand implements ICommand<Promise<SyncObjectStatus>> {
constructor(private obj: ObjDto<ObjRemovedDto>, private tx: BeginTxResponse) {}
constructor(private authUrl: string, private obj: ObjRemovedDto, private tx: BeginTxResponse) {}
// eslint-disable-next-line @typescript-eslint/require-await
async execute(): Promise<SyncObjectStatus> {
fnConsoleLog('SyncRemovedCommand', this.obj, this.tx);

View File

@ -28,7 +28,7 @@ import { ApiSegmentAddCommand } from '../../api/store/segment/api-segment-add.co
import { SyncCryptoFactory } from '../crypto/sync-crypto.factory';
export class SyncSnapshotCommand implements ICommand<Promise<SyncObjectStatus>> {
constructor(private obj: ObjDto<ObjPageDto>, private tx: BeginTxResponse) {}
constructor(private authUrl: string, private obj: ObjDto<ObjPageDto>, private tx: BeginTxResponse) {}
async execute(): Promise<SyncObjectStatus> {
const page = this.obj.data;
const snapshot = page.snapshot;
@ -54,7 +54,7 @@ export class SyncSnapshotCommand implements ICommand<Promise<SyncObjectStatus>>
if (!segment) return;
const content = this.getSegmentContent(segment);
if (!content) return;
await new ApiSegmentAddCommand(this.tx, content, {
await new ApiSegmentAddCommand(this.authUrl, this.tx, content, {
key: await SyncCryptoFactory.newKey(),
type: SyncHashType.PageSnapshotFirstHash,
hash: segment.hash,
@ -65,14 +65,14 @@ export class SyncSnapshotCommand implements ICommand<Promise<SyncObjectStatus>>
private async syncSnapshot(snapshot: PageSnapshotDto, parent: string): Promise<void> {
// snapshot->info
await new ApiSegmentAddCommand(this.tx, JSON.stringify(snapshot.info), {
await new ApiSegmentAddCommand(this.authUrl, this.tx, JSON.stringify(snapshot.info), {
key: await SyncCryptoFactory.newKey(),
type: SyncHashType.PageSnapshotInfoDto,
hash: snapshot.info.hash,
parent
}).execute();
// snapshot->data
await new ApiSegmentAddCommand(this.tx, JSON.stringify(snapshot.data), {
await new ApiSegmentAddCommand(this.authUrl, this.tx, JSON.stringify(snapshot.data), {
key: await SyncCryptoFactory.newKey(),
type: SyncHashType.PageSnapshotDataDto,
hash: snapshot.data.hash,
@ -90,7 +90,7 @@ export class SyncSnapshotCommand implements ICommand<Promise<SyncObjectStatus>>
const content = this.getSegmentContent(segment);
if (!content) return;
await new ApiSegmentAddCommand(this.tx, content, {
await new ApiSegmentAddCommand(this.authUrl, this.tx, content, {
key: await SyncCryptoFactory.newKey(),
type: this.convertSegmentTypeSyncHashType(segment.type),
hash,

View File

@ -32,7 +32,7 @@ import { fnConsoleLog } from '../../../common/fn/fn-console';
import { BeginTxResponse } from '../api/store/api-store.model';
export class SyncIndexCommand implements ICommand<Promise<SyncObjectStatus>> {
constructor(private tx: BeginTxResponse, private id: number) {}
constructor(private authUrl: string, private tx: BeginTxResponse, private id: number) {}
async execute(): Promise<SyncObjectStatus> {
const obj = await new ObjGetCommand(this.id).execute();
@ -46,22 +46,22 @@ export class SyncIndexCommand implements ICommand<Promise<SyncObjectStatus>> {
switch (obj.type) {
case ObjTypeDto.PageSnapshot:
case ObjTypeDto.PageElementSnapshot: {
return await new SyncSnapshotCommand(obj as ObjDto<ObjPageDto>, this.tx).execute();
return await new SyncSnapshotCommand(this.authUrl, obj as ObjDto<ObjPageDto>, this.tx).execute();
}
case ObjTypeDto.PageElementPin: {
return await new SyncPinCommand(obj as ObjDto<ObjPinDto>, this.tx).execute();
return await new SyncPinCommand(this.authUrl, obj as ObjDto<ObjPinDto>, this.tx).execute();
}
case ObjTypeDto.Pdf: {
return await new SyncPdfCommand(obj as ObjDto<ObjPdfDto>, this.tx).execute();
return await new SyncPdfCommand(this.authUrl, obj as ObjDto<ObjPdfDto>, this.tx).execute();
}
case ObjTypeDto.Note: {
return await new SyncNoteCommand(obj as ObjDto<ObjNoteDto>, this.tx).execute();
return await new SyncNoteCommand(this.authUrl, obj as ObjDto<ObjNoteDto>, this.tx).execute();
}
case ObjTypeDto.PageNote: {
return await new SyncPageNoteCommand(obj as ObjDto<ObjPageNoteDto>, this.tx).execute();
return await new SyncPageNoteCommand(this.authUrl, obj as ObjDto<ObjPageNoteDto>, this.tx).execute();
}
case ObjTypeDto.Removed: {
return await new SyncRemovedCommand(obj as ObjDto<ObjRemovedDto>, this.tx).execute();
return await new SyncRemovedCommand(this.authUrl, obj as any as ObjRemovedDto, this.tx).execute();
}
default: {
fnConsoleLog('SyncObjectCommand->PROBLEM', obj, 'index', this.id);

View File

@ -23,6 +23,7 @@ import { fnConsoleLog } from '../../../common/fn/fn-console';
import { ObjDateIndex } from '../../../common/command/obj/index/obj-update-index-add.command';
import { SyncSetProgressCommand } from './progress/sync-set-progress.command';
import { BrowserStorage } from '@pinmenote/browser-api';
import { ApiAuthUrlCommand } from '../api/api-auth-url.command';
export class SyncMonthCommand implements ICommand<Promise<SyncObjectStatus>> {
constructor(private progress: SyncProgress, private yearMonth: string) {}
@ -51,16 +52,17 @@ export class SyncMonthCommand implements ICommand<Promise<SyncObjectStatus>> {
}
async syncIndex(indexList: ObjDateIndex[], start: number): Promise<SyncObjectStatus> {
const begin = await SyncTxHelper.begin();
const authUrl = await new ApiAuthUrlCommand().execute();
const begin = await SyncTxHelper.begin(authUrl);
if (!begin) return SyncObjectStatus.TX_LOCKED;
let i = start;
let status = SyncObjectStatus.OK;
for (i; i < indexList.length; i++) {
status = await new SyncIndexCommand(begin, indexList[i].id).execute();
status = await new SyncIndexCommand(authUrl, begin, indexList[i].id).execute();
switch (status) {
case SyncObjectStatus.SERVER_ERROR: {
fnConsoleLog('SERVER_ERROR !!!!!!!!!!!!!!!!!!!');
await SyncTxHelper.commit();
await SyncTxHelper.commit(authUrl);
await this.updateProgress(indexList[i].id, indexList[i].dt);
return status;
}
@ -94,7 +96,7 @@ export class SyncMonthCommand implements ICommand<Promise<SyncObjectStatus>> {
// all conditions (not current year/month, not last id, object exists) are met
if (resetMonth) await this.updateProgress(-1, lastIndex.dt);
await SyncTxHelper.commit();
await SyncTxHelper.commit(authUrl);
return SyncObjectStatus.OK;
}

View File

@ -21,6 +21,7 @@ import { SyncObjIncomingCommand } from './incoming/sync-obj-incoming.command';
import { SyncSetProgressCommand } from './progress/sync-set-progress.command';
import { SyncProgress } from '../../../common/model/sync.model';
import { SwSyncStore } from '../../sw-sync.store';
import { ApiAuthUrlCommand } from '../api/api-auth-url.command';
export class SyncServerIncomingCommand implements ICommand<Promise<void>> {
constructor(private progress: SyncProgress) {}
@ -28,7 +29,8 @@ export class SyncServerIncomingCommand implements ICommand<Promise<void>> {
if (SwSyncStore.isInSync) return;
SwSyncStore.isInSync = true;
try {
const changesResp = await new ApiObjGetChangesCommand(this.progress.serverId).execute();
const authUrl = await new ApiAuthUrlCommand().execute();
const changesResp = await new ApiObjGetChangesCommand(authUrl, this.progress.serverId).execute();
fnConsoleLog('SyncServerIncomingCommand->START', changesResp);
if ('code' in changesResp) return;
for (let i = 0; i < changesResp.data.length; i++) {

View File

@ -21,6 +21,7 @@ import { fnConsoleLog } from '../../../common/fn/fn-console';
import { SyncResetProgressCommand } from './progress/sync-reset-progress.command';
import { SyncServerIncomingCommand } from './sync-server-incoming.command';
import { SwSyncStore } from '../../sw-sync.store';
import { SyncServerOutgoingCommand } from './sync-server-outgoing.command';
export class SyncServerCommand implements ICommand<Promise<void>> {
async execute(): Promise<void> {

View File

@ -28,10 +28,10 @@ import { TokenDataDto } from '../../../common/model/shared/token.dto';
const SYNC_DELAY = 10_000;
export class SyncTxHelper {
static async begin(): Promise<BeginTxResponse | undefined> {
static async begin(authUrl: string): Promise<BeginTxResponse | undefined> {
const tx = await BrowserStorage.get<BeginTxResponse | undefined>(ObjectStoreKeys.SYNC_TX);
if (tx) return tx;
const txResponse = await new ApiStoreBeginCommand().execute();
const txResponse = await new ApiStoreBeginCommand(authUrl).execute();
if (txResponse?.locked) {
const token = await new TokenStorageGetCommand().execute();
if (!token) return undefined;
@ -53,11 +53,11 @@ export class SyncTxHelper {
return txResponse;
}
static async commit(): Promise<void> {
static async commit(authUrl: string): Promise<void> {
const tx = await BrowserStorage.get<BeginTxResponse | undefined>(ObjectStoreKeys.SYNC_TX);
if (!tx) return;
fnConsoleLog('SyncServerCommand->commit', tx);
await new ApiStoreCommitCommand(tx).execute();
await new ApiStoreCommitCommand(authUrl, tx).execute();
await BrowserStorage.remove(ObjectStoreKeys.SYNC_TX);
}

View File

@ -40,6 +40,7 @@ import { fnConsoleLog } from '../common/fn/fn-console';
import { SyncManualOutgoingCommand } from './command/sync/manual/sync-manual-outgoing.command';
import { SyncServerIncomingCommand } from './command/sync/sync-server-incoming.command';
import { SyncGetProgressCommand } from './command/sync/progress/sync-get-progress.command';
import { ContentExtensionLoginCommand } from './command/content/content-extension-login.command';
const handleMessage = async (
msg: BusMessage<any>,
@ -54,6 +55,9 @@ const handleMessage = async (
if (runtime.id !== BrowserApi.runtime.id) return;
switch (msg.type) {
case BusMessageType.CONTENT_EXTENSION_LOGIN:
await new ContentExtensionLoginCommand(msg.data).execute();
break;
case BusMessageType.CONTENT_DOWNLOAD_DATA:
await new ContentDownloadDataCommand(msg.data).execute();
break;