Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: build on modular core-web #67

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tough-peas-drop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@fedimint/core-web': patch
---

continue modularizing client-web, with standalone and composable components sharing the same web worker resources
98 changes: 98 additions & 0 deletions packages/core-web/src/Base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { WorkerClient } from './worker/WorkerClient'
import { logger, type LogLevel } from './utils/logger'

export abstract class Base {
protected client: WorkerClient

private _openPromise: Promise<void> | null = null
private _resolveOpen: () => void = () => {}
private _isOpen: boolean = false

constructor(client?: WorkerClient, lazy: boolean = false) {
this._openPromise = new Promise((resolve) => {
this._resolveOpen = resolve
})

if (client) {
this.client = client
logger.info(
`${this.constructor.name} instantiated with provided WorkerClient`,
)
} else {
this.client = new WorkerClient()
logger.info(`${this.constructor.name} instantiated with new WorkerClient`)
}

if (!lazy) {
this.initialize()
}
}

async initialize() {
logger.info('Initializing WorkerClient')
await this.client.initialize()
logger.info('WorkerClient initialized')
}

async waitForOpen() {
if (this._isOpen) return Promise.resolve()
return this._openPromise
}

isOpen() {
return this._isOpen
}

protected setOpen(isOpen: boolean) {
this._isOpen = isOpen
if (isOpen) {
this._resolveOpen()
}
}

async open(clientName: string) {
await this.initialize()
if (this.isOpen()) throw new Error('already open.')
const { success } = await this.client.sendSingleMessage('open', {
clientName,
})
if (success) {
this.setOpen(true)
}
return success
}

async joinFederation(inviteCode: string, clientName: string) {
await this.initialize()
if (this.isOpen())
throw new Error(
'already open. You can only call `joinFederation` on closed clients.',
)
const response = await this.client.sendSingleMessage('join', {
inviteCode,
clientName,
})
if (response.success) {
this.setOpen(true)
}
}

/**
* This should ONLY be called when UNLOADING the client.
* After this call, the instance should be discarded.
*/
async cleanup() {
this._openPromise = null
this._isOpen = false
this.client.cleanup()
}

/**
* Sets the log level for the library.
* @param level The desired log level ('DEBUG', 'INFO', 'WARN', 'ERROR', 'NONE').
*/
setLogLevel(level: LogLevel) {
logger.setLevel(level)
logger.info(`Log level set to ${level}.`)
}
}
6 changes: 3 additions & 3 deletions packages/core-web/src/FedimintWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import {
BalanceService,
MintService,
LightningService,
FederationService,
RecoveryService,
} from './services'
import { logger, type LogLevel } from './utils/logger'
import { Federation } from './federation'

const DEFAULT_CLIENT_NAME = 'fm-default' as const

Expand All @@ -16,7 +16,7 @@ export class FedimintWallet {
public balance: BalanceService
public mint: MintService
public lightning: LightningService
public federation: FederationService
public federation: Federation
public recovery: RecoveryService

private _openPromise: Promise<void> | null = null
Expand Down Expand Up @@ -60,7 +60,7 @@ export class FedimintWallet {
this.mint = new MintService(this._client)
this.lightning = new LightningService(this._client)
this.balance = new BalanceService(this._client)
this.federation = new FederationService(this._client)
this.federation = new Federation(this._client)
this.recovery = new RecoveryService(this._client)

logger.info('FedimintWallet instantiated')
Expand Down
99 changes: 99 additions & 0 deletions packages/core-web/src/federation/Federation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { expect, test } from 'vitest'

import { Federation } from './Federation'
import { WorkerClient } from '../worker/WorkerClient'
import { TestingService } from '../test/TestingService'

class TestFederation extends Federation {
public testing: TestingService

constructor() {
super()
this.testing = new TestingService(this.getWorkerClient())
}

// Method to expose the WorkerClient
getWorkerClient(): WorkerClient {
return this['client']
}
}

/**
* Adds Fixtures for setting up and tearing down a test Fedimintfederation instance
*/
const federationTest = test.extend<{ federation: TestFederation }>({
federation: async ({}, use) => {
const randomTestingId = Math.random().toString(36).substring(2, 15)
const federation = new TestFederation()
expect(federation).toBeDefined()

await expect(
federation.joinFederation(
federation.testing.TESTING_INVITE,
randomTestingId,
),
).resolves.toBeUndefined()
await use(federation)

// clear up browser resources
await federation.cleanup()
// remove the federation db
indexedDB.deleteDatabase(randomTestingId)
},
})

federationTest(
'getConfig should return the federation config',
async ({ federation }) => {
expect(federation).toBeDefined()
expect(federation.isOpen()).toBe(true)
const counterBefore = federation.testing.getRequestCounter()
await expect(federation.getConfig()).resolves.toMatchObject({
api_endpoints: expect.any(Object),
broadcast_public_keys: expect.any(Object),
consensus_version: expect.any(Object),
meta: expect.any(Object),
modules: expect.any(Object),
})
expect(federation.testing.getRequestCounter()).toBe(counterBefore + 1)
},
)

federationTest(
'getFederationId should return the federation id',
async ({ federation }) => {
expect(federation).toBeDefined()
expect(federation.isOpen()).toBe(true)

const counterBefore = federation.testing.getRequestCounter()
const federationId = await federation.getFederationId()
expect(federationId).toBeTypeOf('string')
expect(federationId).toHaveLength(64)
expect(federation.testing.getRequestCounter()).toBe(counterBefore + 1)
},
)

federationTest(
'getInviteCode should return the invite code',
async ({ federation }) => {
expect(federation).toBeDefined()
expect(federation.isOpen()).toBe(true)

const counterBefore = federation.testing.getRequestCounter()
const inviteCode = await federation.getInviteCode(0)
expect(inviteCode).toBeTypeOf('string')
expect(federation.testing.getRequestCounter()).toBe(counterBefore + 1)
},
)

federationTest(
'listOperations should return the list of operations',
async ({ federation }) => {
expect(federation).toBeDefined()
expect(federation.isOpen()).toBe(true)

const counterBefore = federation.testing.getRequestCounter()
await expect(federation.listOperations()).resolves.toMatchObject([])
expect(federation.testing.getRequestCounter()).toBe(counterBefore + 1)
},
)
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
import { JSONValue } from '../types/wallet'
import { WorkerClient } from '../worker'
import { Base } from '../Base'

export class FederationService {
constructor(private client: WorkerClient) {}
const DEFAULT_CLIENT_NAME = 'fm-federation-client' as const

export class Federation extends Base {
constructor(client?: WorkerClient, lazy: boolean = false) {
super(client, lazy)
}

async open(clientName: string = DEFAULT_CLIENT_NAME): Promise<boolean> {
return super.open(clientName)
}

async joinFederation(
inviteCode: string,
clientName: string = DEFAULT_CLIENT_NAME,
): Promise<void> {
return super.joinFederation(inviteCode, clientName)
}

async getConfig(): Promise<JSONValue> {
return await this.client.rpcSingle('', 'get_config', {})
Expand Down
1 change: 1 addition & 0 deletions packages/core-web/src/federation/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Federation } from './Federation'
58 changes: 0 additions & 58 deletions packages/core-web/src/services/FederationService.test.ts

This file was deleted.

1 change: 0 additions & 1 deletion packages/core-web/src/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,3 @@ export { MintService } from './MintService'
export { BalanceService } from './BalanceService'
export { LightningService } from './LightningService'
export { RecoveryService } from './RecoveryService'
export { FederationService } from './FederationService'