renamed account interface

This commit is contained in:
balzack 2024-08-28 18:11:42 -07:00
parent 5de105ed89
commit 5c3b491f82
17 changed files with 122 additions and 187 deletions

View File

@ -1,5 +1,5 @@
import { EventEmitter } from 'eventemitter3';
import type { Alias, Account } from '../src/api';
import type { Alias } from '../src/api';
import type { Group } from '../src/types';
export class MockAliasModule implements Alias {

View File

@ -1,5 +1,5 @@
import { EventEmitter } from 'eventemitter3';
import type { Attribute, Account } from '../src/api';
import type { Attribute } from '../src/api';
import type { Article } from '../src/types';
export class MockAttributeModule implements Attribute {

View File

@ -1,5 +1,5 @@
import { EventEmitter } from 'eventemitter3';
import type { Content, Account } from '../src/api';
import type { Content } from '../src/api';
import type { Channel, Topic, Asset, Tag, Repeater } from '../src/types';
export class MockContentModule implements Content {

View File

@ -1,8 +1,8 @@
import { EventEmitter } from 'eventemitter3';
import { type Account } from '../src/api';
import type { AccountStatus } from '../src/types';
import { type Settings } from '../src/api';
import type { Config } from '../src/types';
export class MockAccountModule implements Account {
export class MockSettingsModule implements Settings {
public revision: number;
private emitter: EventEmitter;
@ -12,11 +12,11 @@ export class MockAccountModule implements Account {
this.emitter = new EventEmitter();
}
public addStatusListener(ev: (status: AccountStatus) => void): void {
public addStatusListener(ev: (status: Config) => void): void {
this.emitter.on('status', ev);
}
public removeStatusListener(ev: (status: AccountStatus) => void): void {
public removeStatusListener(ev: (status: Config) => void): void {
this.emitter.off('status', ev);
}

View File

@ -2,7 +2,7 @@ import { DatabagSDK } from '../src/index';
import { type SessionParams } from '../src/types';
import { MockConnection } from '../__mocks__/connection';
import { MockAccountModule } from '../__mocks__/account';
import { MockSettingsModule } from '../__mocks__/settings';
import { MockIdentityModule } from '../__mocks__/identity';
import { MockAliasModule } from '../__mocks__/alias';
import { MockContentModule } from '../__mocks__/content';
@ -29,11 +29,11 @@ jest.mock('../src/connection', () => {
}
})
const mockAccount = new MockAccountModule();
jest.mock('../src/account', () => {
const mockSettings = new MockSettingsModule();
jest.mock('../src/settings', () => {
return {
AccountModule: jest.fn().mockImplementation(() => {
return mockAccount;
SettingsModule: jest.fn().mockImplementation(() => {
return mockSettings;
})
}
})
@ -99,14 +99,14 @@ test('allocates session correctly', async () => {
const params: SessionParams = { topicBatch: 0, tagBatch: 0, channelTypes: [], pushType: '', deviceToken: '', notifications: [], deviceId: '', version: '', appName: '' };
const session = await sdk.login('handle', 'password', 'jest.test', true, null, params);
session.addStatusListener((ev: string) => { status = ev; });
const account = session.getAccount();
account.enableNotifications();
const settings = session.getSettings();
settings.enableNotifications();
mockConnection.emitStatus('connected');
mockConnection.emitRevision({ account: 3, profile: 3, article: 3, group: 3, channel: 3, card: 3});
mockConnection.emitRing({ cardId: '', callId: 'test', calleeToken: '', ice: []});
await waitFor(() => (status === 'connected'));
await waitFor(() => (mockRing.call?.callId === 'test'));
await waitFor(() => (mockAccount.revision == 3));
await waitFor(() => (mockSettings.revision == 3));
await waitFor(() => (mockIdentity.revision == 3));
await waitFor(() => (mockContent.revision == 3));
await waitFor(() => (mockContact.revision == 3));

View File

@ -1,13 +1,13 @@
import { AccountModule } from '../src/account';
import { SettingsModule } from '../src/settings';
import { NoStore } from '../src/store';
import { Crypto } from '../src/crypto';
import { ConsoleLogging } from '../src/logging';
import { defaultAccountEntity } from '../src/entities';
import { AccountStatus } from '../src/types';
import { defaultConfigEntity } from '../src/entities';
import { Config } from '../src/types';
import { waitFor } from '../__mocks__/waitFor';
import axios from 'redaxios';
const testStatus = JSON.parse(JSON.stringify(defaultAccountEntity));
const testStatus = JSON.parse(JSON.stringify(defaultConfigEntity));
jest.mock('redaxios', () => {
return {
@ -76,29 +76,29 @@ class TestStore extends NoStore {
}
test('allocates session correctly', async () => {
let status: AccountStatus | null = null;
let status: Config | null = null;
const log = new ConsoleLogging();
const store = new TestStore();
const crypto = new TestCrypto();
const account = new AccountModule(log, store, crypto, 'test_guid', 'test_token', 'test_url', false);
account.addStatusListener((ev: AccountStatus) => { status = ev });
account.setRevision(5);
const settings = new SettingsModule(log, store, crypto, 'test_guid', 'test_token', 'test_url', false);
settings.addStatusListener((ev: Config) => { status = ev });
settings.setRevision(5);
await waitFor(() => (status?.storageUsed == 2));
account.enableRegistry();
account.setRevision(6);
settings.enableRegistry();
settings.setRevision(6);
await waitFor(() => Boolean(status?.searchable));
account.disableRegistry();
account.setRevision(7);
settings.disableRegistry();
settings.setRevision(7);
await waitFor(() => !Boolean(status?.searchable));
account.enableNotifications();
account.setRevision(8);
settings.enableNotifications();
settings.setRevision(8);
await waitFor(() => Boolean(status?.pushEnabled));
account.disableNotifications();
account.setRevision(9);
settings.disableNotifications();
settings.setRevision(9);
await waitFor(() => !Boolean(status?.pushEnabled));
account.setSeal('password');
account.setRevision(10);
settings.setSeal('password');
settings.setRevision(10);
await waitFor(() => Boolean(status?.sealSet));
});

View File

@ -1,5 +1,5 @@
import { EventEmitter } from 'eventemitter3';
import type { Alias, Account, Logging } from './api';
import type { Alias, Settings, Logging } from './api';
import type { Group } from './types';
import { Store } from './store';
@ -10,16 +10,16 @@ export class AliasModule implements Alias {
private token: string;
private node: string;
private secure: boolean;
private account: Account;
private settings: Settings;
private emitter: EventEmitter;
constructor(log: Logging, account: Account, store: Store, guid: string, token: string, node: string, secure: boolean) {
constructor(log: Logging, settings: Settings, store: Store, guid: string, token: string, node: string, secure: boolean) {
this.guid = guid;
this.token = token;
this.node = node;
this.secure = secure;
this.log = log;
this.account = account;
this.settings = settings;
this.emitter = new EventEmitter();
}

View File

@ -4,54 +4,10 @@
// formaize delete vs block remote channel
// articles share by cards now
import type { Channel, Topic, Asset, Tag, Article, Group, Card, Profile, Call, AccountStatus, NodeConfig, NodeAccount, Repeater } from './types';
export interface SqlStore {
set(stmt: string, params?: (string | number | null)[]): Promise<void>;
get(stmt: string, params?: (string | number | null)[]): Promise<any[]>;
}
export interface WebStore {
getValue(key: string): Promise<string>;
setValue(key: string, value: string): Promise<void>;
clearValue(key: string): Promise<void>;
clearAll(): Promise<void>;
}
export interface Crypto {
// generate salt for pbk function
pkdkfSalt(): { saltHex: string };
// generate aes key with pbkdf2
pbkdfKey(saltHex: string, password: string): { aesKeyHex: string };
// generate random aes key
aesKey(): { aesKeyHex: string };
// generate iv to use to aes function
aesIv(): { ivHex: string };
// encrypt data with aes key and iv
aesEncrypt(data: string, ivHex: string, aesKeyHex: string): { encryptedDataB64: string };
// decrypt data with aes key and iv
aesDecrypt(encryptedDataB64: string, ivHex: string, aesKeyHex: string): { data: string };
// generate rsa key
rsaKey(): { publicKeyB64: string, privateKeyB64: string };
// encrypt data with public rsa key
rsaEncrypt(data: string, publicKeyB64: string): { encryptedDataB64: string }
// decrypt data with private rsa key
rsaDecrypt(encryptedDataB64: string, privateKeyB64: string): { data: string }
}
import type { Channel, Topic, Asset, Tag, Article, Group, Card, Profile, Call, Config, NodeConfig, NodeAccount, Repeater } from './types';
export interface Session {
close(): Promise<{ node: string, secure: boolean, token: string }>;
getAccount(): Account;
getSettings(): Settings;
getIdentity(): Identity;
getContact(): Contact;
getAlias(): Alias;
@ -63,8 +19,6 @@ export interface Session {
addFocus(cardId: string | null, channelId: string): Focus;
removeFocus(focus: Focus): void;
resync(): void;
addStatusListener(ev: (status: string) => void): void;
removeStatusListener(ev: (status: string) => void): void;
}
@ -82,7 +36,7 @@ export interface Ring {
decline(callId: string): void;
}
export interface Account {
export interface Settings {
setLogin(username: string, password: string): Promise<void>;
enableNotifications(): Promise<void>;
disableNotifications(): Promise<void>;
@ -96,8 +50,8 @@ export interface Account {
unlockSeal(password: string): Promise<void>;
forgetSeal(): Promise<void>;
addStatusListener(ev: (status: AccountStatus) => void): void;
removeStatusListener(ev: (status: AccountStatus) => void): void;
addStatusListener(ev: (config: Config) => void): void;
removeStatusListener(ev: (config: Config) => void): void;
}
export interface Identity {

View File

@ -1,5 +1,5 @@
import { EventEmitter } from 'eventemitter3';
import type { Attribute, Account, Logging } from './api';
import type { Attribute, Settings, Logging } from './api';
import type { Article } from './types';
import { Store } from './store';
@ -10,16 +10,16 @@ export class AttributeModule implements Attribute {
private token: string;
private node: string;
private secure: boolean;
private account: Account;
private settings: Settings;
private emitter: EventEmitter;
constructor(log: Logging, account: Account, store: Store, guid: string, token: string, node: string, secure: boolean) {
constructor(log: Logging, settings: Settings, store: Store, guid: string, token: string, node: string, secure: boolean) {
this.guid = guid;
this.token = token;
this.node = node;
this.secure = secure;
this.log = log;
this.account = account;
this.settings = settings;
this.emitter = new EventEmitter();
}

View File

@ -1,5 +1,5 @@
import { EventEmitter } from 'eventemitter3';
import type { Content, Account, Logging } from './api';
import type { Content, Settings, Logging } from './api';
import type { Channel, Topic, Asset, Tag, Repeater } from './types';
import { Store } from './store';
@ -10,16 +10,16 @@ export class ContentModule implements Content {
private token: string;
private node: string;
private secure: boolean;
private account: Account;
private settings: Settings;
private emitter: EventEmitter;
constructor(log: Logging, account: Account, store: Store, guid: string, token: string, node: string, secure: boolean) {
constructor(log: Logging, settings: Settings, store: Store, guid: string, token: string, node: string, secure: boolean) {
this.guid = guid;
this.token = token;
this.node = node;
this.secure = secure;
this.log = log;
this.account = account;
this.settings = settings;
this.emitter = new EventEmitter();
}

View File

@ -149,7 +149,7 @@ export type SealEntity = {
publicKey: string,
}
export type AccountEntity = {
export type ConfigEntity = {
disabled: boolean,
storageUsed: number,
storageAvailable: number,
@ -164,7 +164,7 @@ export type AccountEntity = {
webPushKey: string,
}
export const defaultAccountEntity = {
export const defaultConfigEntity = {
disabled: false,
storageUsed: 0,
storageAvailable: 0,

View File

@ -10,13 +10,16 @@ import { addAccount } from './net/addAccount';
import { setAdmin } from './net/setAdmin';
import { getAvailable } from './net/getAvailable';
import { getUsername } from './net/getUsername';
import type { Session, Node, Bot, SqlStore, WebStore } from './api';
import type { Session, Node, Bot } from './api';
import type { SessionParams } from './types';
import type { Login } from './entities';
import type { Crypto } from './crypto';
import type { WebStore, SqlStore } from './store';
export * from './api';
export * from './types';
export { WebStore, SqlStore } from './store';
export { Crypto } from './crypto';
export class DatabagSDK {
@ -77,7 +80,8 @@ export class DatabagSDK {
}
public async logout(session: Session, all: boolean): Promise<void> {
const params = await session.close();
const sessionModule = session as SessionModule;
const params = await sessionModule.close();
try {
const { node, secure, token } = params;
await clearLogin(node, secure, token, all);

View File

@ -1,7 +1,7 @@
import axios from 'redaxios';
import { AccountEntity } from '../entities';
import { ConfigEntity } from '../entities';
export async function getAccountStatus(node: string, secure: boolean, token: string): Promise<AccountEntity> {
export async function getAccountStatus(node: string, secure: boolean, token: string): Promise<ConfigEntity> {
const endpoint = `http${secure ? 's' : ''}://${node}/account/status?agent=${token}`;
const response = await axios.get(endpoint);
if (response.status >= 400 && response.status < 600) {

View File

@ -1,6 +1,6 @@
import { EventEmitter } from 'eventemitter3';
import { AccountModule } from './account';
import { SettingsModule } from './settings';
import { IdentityModule } from './identity';
import { ContactModule } from './contact';
import { AliasModule } from './alias';
@ -12,7 +12,7 @@ import { RingModule } from './ring';
import { Connection } from './connection';
import type { Session, Account, Identity, Contact, Ring, Alias, Attribute, Content, Stream, Focus } from './api';
import type { Session, Settings, Identity, Contact, Ring, Alias, Attribute, Content, Stream, Focus } from './api';
import { Revision } from './entities';
import { Call } from './types';
import { Store } from './store';
@ -30,9 +30,8 @@ export class SessionModule implements Session {
private node: string;
private secure: boolean;
private loginTimestamp: number;
private syncRevision: Revision | null;
private status: string;
private account: AccountModule;
private settings: SettingsModule;
private identity: IdentityModule;
private contact: ContactModule;
private alias: AliasModule;
@ -54,16 +53,15 @@ export class SessionModule implements Session {
this.node = node;
this.secure = secure;
this.loginTimestamp = loginTimestamp;
this.syncRevision = null;
this.status = 'connecting'
this.emitter = new EventEmitter();
this.identity = new IdentityModule(log, this.store, guid, token, node, secure);
this.account = new AccountModule(log, this.store, this.crypto, guid, token, node, secure);
this.settings = new SettingsModule(log, this.store, this.crypto, guid, token, node, secure);
this.contact = new ContactModule(log, this.store, guid, token, node, secure);
this.alias = new AliasModule(log, this.account, this.store, guid, token, node, secure);
this.attribute = new AttributeModule(log, this.account, this.store, guid, token, node, secure);
this.content = new ContentModule(log, this.account, this.store, guid, token, node, secure);
this.alias = new AliasModule(log, this.settings, this.store, guid, token, node, secure);
this.attribute = new AttributeModule(log, this.settings, this.store, guid, token, node, secure);
this.content = new ContentModule(log, this.settings, this.store, guid, token, node, secure);
this.stream = new StreamModule(log, this.contact, this.content, this.store, guid);
this.ring = new RingModule(log);
this.connection = new Connection(log, token, node, secure);
@ -74,23 +72,12 @@ export class SessionModule implements Session {
}
const onRevision = async (ev: Revision) => {
try {
await this.identity.setRevision(ev.profile);
await this.account.setRevision(ev.account);
await this.contact.setRevision(ev.card);
await this.attribute.setRevision(ev.article);
await this.alias.setRevision(ev.group);
await this.content.setRevision(ev.channel);
if (this.syncRevision) {
this.syncRevision = null
this.emitter.emit('status', this.getStatus());
}
}
catch(err) {
this.log.warn(err);
this.syncRevision = ev;
this.emitter.emit('status', this.getStatus());
}
await this.identity.setRevision(ev.profile);
await this.settings.setRevision(ev.account);
await this.contact.setRevision(ev.card);
await this.attribute.setRevision(ev.article);
await this.alias.setRevision(ev.group);
await this.content.setRevision(ev.channel);
}
const onRing = (ev: Call) => {
@ -111,45 +98,24 @@ export class SessionModule implements Session {
}
private getStatus(): string {
if (this.status === 'connected' && this.syncRevision) {
return 'offsync';
}
return this.status;
}
public async resync() {
if (this.syncRevision) {
try {
await this.identity.setRevision(this.syncRevision.profile);
await this.account.setRevision(this.syncRevision.account);
await this.contact.setRevision(this.syncRevision.card);
await this.attribute.setRevision(this.syncRevision.article);
await this.alias.setRevision(this.syncRevision.group);
await this.content.setRevision(this.syncRevision.channel);
this.syncRevision = null
this.emitter.emit('status', this.getStatus());
}
catch(err) {
this.log.warn(err);
}
}
}
public async close(): Promise<{ node: string, secure: boolean, token: string }> {
await this.content.close();
await this.attribute.close();
await this.alias.close();
await this.contact.close();
await this.identity.close();
await this.account.close();
await this.settings.close();
await this.stream.close();
this.connection.close();
const { node, secure, token } = this;
return { node: node, secure: secure, token: token };
}
public getAccount(): Account {
return this.account;
public getSettings(): Settings {
return this.settings;
}
public getIdentity(): Identity {

View File

@ -1,10 +1,10 @@
import { EventEmitter } from 'eventemitter3';
import type { Account } from './api';
import type { AccountStatus } from './types';
import type { Settings } from './api';
import type { Config } from './types';
import { Store } from './store';
import { Crypto } from './crypto';
import { Logging } from './logging';
import { defaultAccountEntity, AccountEntity } from './entities';
import { defaultConfigEntity, ConfigEntity } from './entities';
import { getAccountStatus } from './net/getAccountStatus';
import { addAccountMFAuth } from './net/addAccountMFAuth';
import { setAccountMFAuth } from './net/setAccountMFAuth';
@ -18,7 +18,7 @@ import { clearAccountSeal } from './net/clearAccountSeal';
const CLOSE_POLL_MS = 100;
const RETRY_POLL_MS = 2000;
export class AccountModule implements Account {
export class SettingsModule implements Settings {
private emitter: EventEmitter;
private guid: string;
@ -32,7 +32,7 @@ export class AccountModule implements Account {
private closing: boolean;
private revision: number;
private nextRevision: number | null;
private entity: AccountEntity;
private entity: ConfigEntity;
private sealKey: { privateKey: string, publicKey: string } | null;
constructor(log: Logging, store: Store, crypto: Crypto | null, guid: string, token: string, node: string, secure: boolean) {
@ -46,7 +46,7 @@ export class AccountModule implements Account {
this.sealKey = null;
this.secure = secure;
this.revision = 0;
this.entity = defaultAccountEntity;
this.entity = defaultConfigEntity;
this.syncing = true;
this.closing = false;
this.nextRevision = null;
@ -54,8 +54,8 @@ export class AccountModule implements Account {
}
private async init() {
this.revision = await this.store.getAccountRevision(this.guid);
this.entity = await this.store.getAccountData(this.guid);
this.revision = await this.store.getSettingsRevision(this.guid);
this.entity = await this.store.getSettingsData(this.guid);
this.sealKey = await this.store.getSeal(this.guid);
this.syncing = false;
await this.sync();
@ -73,8 +73,8 @@ export class AccountModule implements Account {
try {
const { guid, node, secure, token } = this;
const status = await getAccountStatus(node, secure, token);
await this.store.setAccountData(guid, status);
await this.store.setAccountRevision(guid, nextRev);
await this.store.setSettingsData(guid, status);
await this.store.setSettingsRevision(guid, nextRev);
this.entity = status;
this.emitter.emit('status', this.getStatus());
this.revision = nextRev;
@ -101,12 +101,12 @@ export class AccountModule implements Account {
return { storageUsed, storageAvailable, forwardingAddress, searchable, allowUnsealed, pushEnabled, sealable, sealSet, sealUnlocked, enableIce, multiFactorAuth, webPushKey };
}
public addStatusListener(ev: (status: AccountStatus) => void): void {
public addStatusListener(ev: (status: Config) => void): void {
this.emitter.on('status', ev);
this.emitter.emit('status', this.getStatus());
}
public removeStatusListener(ev: (status: AccountStatus) => void): void {
public removeStatusListener(ev: (status: Config) => void): void {
this.emitter.off('status', ev);
}

View File

@ -1,5 +1,4 @@
import { WebStore, SqlStore } from './api';
import { Login, ProfileEntity, defaultProfileEntity, AccountEntity, defaultAccountEntity } from './entities';
import { Login, ProfileEntity, defaultProfileEntity, ConfigEntity, defaultConfigEntity } from './entities';
import type { Logging } from './logging';
export interface Store {
@ -15,10 +14,22 @@ export interface Store {
getProfileData(guid: string): Promise<ProfileEntity>;
setProfileData(guid: string, data: ProfileEntity): Promise<void>;
getAccountRevision(guid: string): Promise<number>;
setAccountRevision(guid: string, revision: number): Promise<void>;
getAccountData(guid: string): Promise<AccountEntity>;
setAccountData(guid: string, data: AccountEntity): Promise<void>;
getSettingsRevision(guid: string): Promise<number>;
setSettingsRevision(guid: string, revision: number): Promise<void>;
getSettingsData(guid: string): Promise<ConfigEntity>;
setSettingsData(guid: string, data: ConfigEntity): Promise<void>;
}
export interface SqlStore {
set(stmt: string, params?: (string | number | null)[]): Promise<void>;
get(stmt: string, params?: (string | number | null)[]): Promise<any[]>;
}
export interface WebStore {
getValue(key: string): Promise<string>;
setValue(key: string, value: string): Promise<void>;
clearValue(key: string): Promise<void>;
clearAll(): Promise<void>;
}
export class OfflineStore implements Store {
@ -102,19 +113,19 @@ export class OfflineStore implements Store {
await this.setAppValue(guid, 'profile_data', JSON.stringify(data));
}
public async getAccountRevision(guid: string): Promise<number> {
public async getSettingsRevision(guid: string): Promise<number> {
return await this.getAppValue(guid, 'account_revision', 0) as number;
}
public async setAccountRevision(guid: string, revision: number): Promise<void> {
public async setSettingsRevision(guid: string, revision: number): Promise<void> {
await this.setAppValue(guid, 'account_revision', revision.toString());
}
public async getAccountData(guid: string): Promise<AccountEntity> {
return await this.getAppValue(guid, 'account_data', defaultAccountEntity) as AccountEntity;
public async getSettingsData(guid: string): Promise<ConfigEntity> {
return await this.getAppValue(guid, 'account_data', defaultConfigEntity) as ConfigEntity;
}
public async setAccountData(guid: string, data: AccountEntity): Promise<void> {
public async setSettingsData(guid: string, data: ConfigEntity): Promise<void> {
await this.setAppValue(guid, 'account_data', JSON.stringify(data));
}
@ -184,18 +195,18 @@ export class OnlineStore implements Store {
public async setProfileData(guid: string, data: ProfileEntity): Promise<void> {
}
public async getAccountRevision(guid: string): Promise<number> {
public async getSettingsRevision(guid: string): Promise<number> {
return 0;
}
public async setAccountRevision(guid: string, revision: number): Promise<void> {
public async setSettingsRevision(guid: string, revision: number): Promise<void> {
}
public async getAccountData(guid: string): Promise<AccountEntity> {
return defaultAccountEntity;
public async getSettingsData(guid: string): Promise<ConfigEntity> {
return defaultConfigEntity;
}
public async setAccountData(guid: string, data: AccountEntity): Promise<void> {
public async setSettingsData(guid: string, data: ConfigEntity): Promise<void> {
}
}
@ -238,18 +249,18 @@ export class NoStore implements Store {
public async setProfileData(guid: string, data: ProfileEntity): Promise<void> {
}
public async getAccountRevision(guid: string): Promise<number> {
public async getSettingsRevision(guid: string): Promise<number> {
return 0;
}
public async setAccountRevision(guid: string, revision: number): Promise<void> {
public async setSettingsRevision(guid: string, revision: number): Promise<void> {
}
public async getAccountData(guid: string): Promise<AccountEntity> {
return defaultAccountEntity;
public async getSettingsData(guid: string): Promise<ConfigEntity> {
return defaultConfigEntity;
}
public async setAccountData(guid: string, data: AccountEntity): Promise<void> {
public async setSettingsData(guid: string, data: ConfigEntity): Promise<void> {
}
}

View File

@ -135,7 +135,7 @@ export type Repeater = {
server: string,
}
export type AccountStatus = {
export type Config = {
disabled: boolean,
storageUsed: number,
storageAvailable: number,