@fedimint/core-web 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/FedimintWallet.d.ts +9 -4
- package/dist/FedimintWallet.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/services/BalanceService.d.ts +26 -0
- package/dist/services/BalanceService.d.ts.map +1 -1
- package/dist/services/FederationService.d.ts +0 -1
- package/dist/services/FederationService.d.ts.map +1 -1
- package/dist/services/LightningService.d.ts +6 -5
- package/dist/services/LightningService.d.ts.map +1 -1
- package/dist/services/MintService.d.ts +3 -3
- package/dist/services/MintService.d.ts.map +1 -1
- package/dist/types/wallet.d.ts +24 -11
- package/dist/types/wallet.d.ts.map +1 -1
- package/dist/utils/logger.d.ts +17 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/worker/WorkerClient.d.ts +3 -0
- package/dist/worker/WorkerClient.d.ts.map +1 -1
- package/dist/worker.js +1 -1
- package/dist/worker.js.map +1 -1
- package/package.json +4 -6
- package/src/FedimintWallet.test.ts +12 -9
- package/src/FedimintWallet.ts +35 -22
- package/src/services/BalanceService.test.ts +50 -0
- package/src/services/BalanceService.ts +26 -0
- package/src/services/FederationService.test.ts +58 -0
- package/src/services/FederationService.ts +0 -10
- package/src/services/LightningService.test.ts +168 -0
- package/src/services/LightningService.ts +21 -5
- package/src/services/MintService.test.ts +19 -0
- package/src/services/MintService.ts +38 -18
- package/src/test/TestFedimintWallet.ts +17 -0
- package/src/test/TestingService.ts +59 -0
- package/src/test/setupTests.ts +43 -0
- package/src/types/wallet.ts +32 -13
- package/src/utils/logger.ts +61 -0
- package/src/worker/WorkerClient.ts +41 -2
- package/src/worker/worker.js +67 -56
- package/src/worker/worker.test.ts +90 -0
- package/node_modules/@fedimint/fedimint-client-wasm/fedimint_client_wasm.d.ts +0 -49
- package/node_modules/@fedimint/fedimint-client-wasm/fedimint_client_wasm.js +0 -4
- package/node_modules/@fedimint/fedimint-client-wasm/fedimint_client_wasm_bg.js +0 -1411
- package/node_modules/@fedimint/fedimint-client-wasm/fedimint_client_wasm_bg.wasm +0 -0
- package/node_modules/@fedimint/fedimint-client-wasm/package.json +0 -23
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { WorkerClient } from '../worker'
|
|
2
|
+
|
|
3
|
+
export const TESTING_INVITE =
|
|
4
|
+
'fed11qgqrsdnhwden5te0v9cxjtt4dekxzamxw4kz6mmjvvkhydted9ukg6r9xfsnx7th0fhn26tf093juamwv4u8gtnpwpcz7qqpyz0e327ua8geceutfrcaezwt22mk6s2rdy09kg72jrcmncng2gn0kp2m5sk'
|
|
5
|
+
|
|
6
|
+
// This is a testing service that allows for inspecting the internals
|
|
7
|
+
// of the WorkerClient. It is not intended for use in production.
|
|
8
|
+
export class TestingService {
|
|
9
|
+
public TESTING_INVITE: string
|
|
10
|
+
constructor(private client: WorkerClient) {
|
|
11
|
+
// Solo Mint on mutinynet
|
|
12
|
+
this.TESTING_INVITE = TESTING_INVITE
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
getRequestCounter() {
|
|
16
|
+
return this.client._getRequestCounter()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getRequestCallbackMap() {
|
|
20
|
+
return this.client._getRequestCallbackMap()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async payWithFaucet(invoice: string) {
|
|
24
|
+
try {
|
|
25
|
+
const response = await fetch(
|
|
26
|
+
`https://faucet.mutinynet.com/api/lnurlw/callback?k1=k1&pr=${invoice}`,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
if (!response.ok) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`HTTP error! Failed to pay invoice. status: ${response.status}`,
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return await response.json()
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error('Error paying with faucet', error)
|
|
38
|
+
throw error
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async getExternalInvoice(amount: number) {
|
|
43
|
+
try {
|
|
44
|
+
const response = await fetch(
|
|
45
|
+
`https://lnurl-staging.mutinywallet.com/lnurlp/refund/callback?amount=${amount}`,
|
|
46
|
+
)
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`HTTP error! Failed to get external invoice. status: ${response.status}`,
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return await response.json()
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error('Error getting external invoice', error)
|
|
56
|
+
throw error
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { expect, test } from 'vitest'
|
|
2
|
+
import { TestFedimintWallet } from './TestFedimintWallet'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Adds Fixtures for setting up and tearing down a test FedimintWallet instance
|
|
6
|
+
*/
|
|
7
|
+
export const walletTest = test.extend<{ wallet: TestFedimintWallet }>({
|
|
8
|
+
wallet: async ({}, use) => {
|
|
9
|
+
const randomTestingId = Math.random().toString(36).substring(2, 15)
|
|
10
|
+
const wallet = new TestFedimintWallet()
|
|
11
|
+
expect(wallet).toBeDefined()
|
|
12
|
+
|
|
13
|
+
await expect(
|
|
14
|
+
wallet.joinFederation(wallet.testing.TESTING_INVITE, randomTestingId),
|
|
15
|
+
).resolves.toBeUndefined()
|
|
16
|
+
await use(wallet)
|
|
17
|
+
|
|
18
|
+
// clear up browser resources
|
|
19
|
+
await wallet.cleanup()
|
|
20
|
+
// remove the wallet db
|
|
21
|
+
indexedDB.deleteDatabase(randomTestingId)
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Adds Fixtures for setting up and tearing down a test Worker instance
|
|
27
|
+
*/
|
|
28
|
+
export const workerTest = test.extend<{
|
|
29
|
+
worker: Worker
|
|
30
|
+
clientName: string
|
|
31
|
+
}>({
|
|
32
|
+
worker: async ({}, use) => {
|
|
33
|
+
const worker = new Worker(new URL('../worker/worker.js', import.meta.url), {
|
|
34
|
+
type: 'module',
|
|
35
|
+
})
|
|
36
|
+
await use(worker)
|
|
37
|
+
worker.terminate()
|
|
38
|
+
},
|
|
39
|
+
clientName: async ({}, use) => {
|
|
40
|
+
const randomTestingId = Math.random().toString(36).substring(2, 15)
|
|
41
|
+
await use(randomTestingId)
|
|
42
|
+
},
|
|
43
|
+
})
|
package/src/types/wallet.ts
CHANGED
|
@@ -47,22 +47,22 @@ type PayType = {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
type LnPayState =
|
|
50
|
-
| '
|
|
51
|
-
| '
|
|
52
|
-
| {
|
|
53
|
-
| {
|
|
54
|
-
| '
|
|
50
|
+
| 'created'
|
|
51
|
+
| 'canceled'
|
|
52
|
+
| { funded: { block_height: number } }
|
|
53
|
+
| { waiting_for_refund: { error_reason: string } }
|
|
54
|
+
| 'awaiting_change'
|
|
55
55
|
| { Success: { preimage: string } }
|
|
56
|
-
| {
|
|
57
|
-
| {
|
|
56
|
+
| { refunded: { gateway_error: string } }
|
|
57
|
+
| { unexpected_error: { error_message: string } }
|
|
58
58
|
|
|
59
59
|
type LnReceiveState =
|
|
60
|
-
| '
|
|
61
|
-
| {
|
|
62
|
-
| {
|
|
63
|
-
| '
|
|
64
|
-
| '
|
|
65
|
-
| '
|
|
60
|
+
| 'created'
|
|
61
|
+
| { waiting_for_payment: { invoice: string; timeout: number } }
|
|
62
|
+
| { canceled: { reason: string } }
|
|
63
|
+
| 'funded'
|
|
64
|
+
| 'awaiting_funds'
|
|
65
|
+
| 'claimed'
|
|
66
66
|
|
|
67
67
|
type CreateBolt11Response = {
|
|
68
68
|
operation_id: string
|
|
@@ -94,6 +94,22 @@ type StreamResult<T extends JSONValue> =
|
|
|
94
94
|
|
|
95
95
|
type CancelFunction = () => void
|
|
96
96
|
|
|
97
|
+
type ReissueExternalNotesState =
|
|
98
|
+
| 'Created'
|
|
99
|
+
| 'Issuing'
|
|
100
|
+
| 'Done'
|
|
101
|
+
| { Failed: { error: string } }
|
|
102
|
+
|
|
103
|
+
type Duration = {
|
|
104
|
+
nanos: number
|
|
105
|
+
secs: number
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
type MintSpendNotesResponse = {
|
|
109
|
+
notes: string
|
|
110
|
+
operation_id: string
|
|
111
|
+
}
|
|
112
|
+
|
|
97
113
|
export {
|
|
98
114
|
JSONValue,
|
|
99
115
|
JSONObject,
|
|
@@ -111,4 +127,7 @@ export {
|
|
|
111
127
|
StreamResult,
|
|
112
128
|
ModuleKind,
|
|
113
129
|
CancelFunction,
|
|
130
|
+
ReissueExternalNotesState,
|
|
131
|
+
Duration,
|
|
132
|
+
MintSpendNotesResponse,
|
|
114
133
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const logLevels = ['debug', 'info', 'warn', 'error', 'none'] as const
|
|
2
|
+
export type LogLevel = (typeof logLevels)[number]
|
|
3
|
+
|
|
4
|
+
export class Logger {
|
|
5
|
+
private level: LogLevel
|
|
6
|
+
|
|
7
|
+
constructor(level: LogLevel = 'none') {
|
|
8
|
+
this.level = level
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
setLevel(level: LogLevel) {
|
|
12
|
+
this.level = level
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
coerceLevel(level: string): LogLevel {
|
|
16
|
+
if (logLevels.includes(level.toLocaleUpperCase() as LogLevel)) {
|
|
17
|
+
return level.toLocaleUpperCase() as LogLevel
|
|
18
|
+
}
|
|
19
|
+
return 'info'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
log(level: string, message: string, ...args: any[]) {
|
|
23
|
+
const logLevel = this.coerceLevel(level)
|
|
24
|
+
if (!this.shouldLog(logLevel)) {
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
const consoleFn = console[logLevel]
|
|
28
|
+
consoleFn(`[${logLevel.toUpperCase()}] ${message}`, ...args)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
debug(message: string, ...args: any[]) {
|
|
32
|
+
this.log('debug', message, ...args)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
info(message: string, ...args: any[]) {
|
|
36
|
+
this.log('info', message, ...args)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
warn(message: string, ...args: any[]) {
|
|
40
|
+
this.log('warn', message, ...args)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
error(message: string, ...args: any[]) {
|
|
44
|
+
this.log('error', message, ...args)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private shouldLog(
|
|
48
|
+
messageLevel: LogLevel,
|
|
49
|
+
): messageLevel is Exclude<LogLevel, 'none'> {
|
|
50
|
+
const levels: LogLevel[] = ['debug', 'info', 'warn', 'error', 'none']
|
|
51
|
+
const messageLevelIndex = levels.indexOf(messageLevel)
|
|
52
|
+
const currentLevelIndex = levels.indexOf(this.level)
|
|
53
|
+
return (
|
|
54
|
+
currentLevelIndex <= messageLevelIndex &&
|
|
55
|
+
this.level !== 'none' &&
|
|
56
|
+
messageLevel !== 'none'
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const logger = new Logger()
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
StreamError,
|
|
6
6
|
StreamResult,
|
|
7
7
|
} from '../types/wallet'
|
|
8
|
+
import { logger } from '../utils/logger'
|
|
8
9
|
|
|
9
10
|
// Handles communication with the wasm worker
|
|
10
11
|
// TODO: Move rpc stream management to a separate "SubscriptionManager" class
|
|
@@ -21,6 +22,8 @@ export class WorkerClient {
|
|
|
21
22
|
})
|
|
22
23
|
this.worker.onmessage = this.handleWorkerMessage.bind(this)
|
|
23
24
|
this.worker.onerror = this.handleWorkerError.bind(this)
|
|
25
|
+
logger.info('WorkerClient instantiated')
|
|
26
|
+
logger.debug('WorkerClient', this.worker)
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
// Idempotent setup - Loads the wasm module
|
|
@@ -30,16 +33,31 @@ export class WorkerClient {
|
|
|
30
33
|
return this.initPromise
|
|
31
34
|
}
|
|
32
35
|
|
|
36
|
+
private handleWorkerLogs(event: MessageEvent) {
|
|
37
|
+
const { type, level, message, ...data } = event.data
|
|
38
|
+
logger.log(level, message, ...data)
|
|
39
|
+
}
|
|
40
|
+
|
|
33
41
|
private handleWorkerError(event: ErrorEvent) {
|
|
34
|
-
|
|
42
|
+
logger.error('Worker error', event)
|
|
35
43
|
}
|
|
36
44
|
|
|
37
45
|
private handleWorkerMessage(event: MessageEvent) {
|
|
38
46
|
const { type, requestId, ...data } = event.data
|
|
47
|
+
if (type === 'log') {
|
|
48
|
+
this.handleWorkerLogs(event.data)
|
|
49
|
+
}
|
|
39
50
|
const streamCallback = this.requestCallbacks.get(requestId)
|
|
40
51
|
// TODO: Handle errors... maybe have another callbacks list for errors?
|
|
52
|
+
logger.debug('WorkerClient - handleWorkerMessage', event.data)
|
|
41
53
|
if (streamCallback) {
|
|
42
54
|
streamCallback(data) // {data: something} OR {error: something}
|
|
55
|
+
} else {
|
|
56
|
+
logger.warn(
|
|
57
|
+
'WorkerClient - handleWorkerMessage - received message with no callback',
|
|
58
|
+
requestId,
|
|
59
|
+
event.data,
|
|
60
|
+
)
|
|
43
61
|
}
|
|
44
62
|
}
|
|
45
63
|
|
|
@@ -50,10 +68,22 @@ export class WorkerClient {
|
|
|
50
68
|
sendSingleMessage(type: string, payload?: any): Promise<any> {
|
|
51
69
|
return new Promise((resolve, reject) => {
|
|
52
70
|
const requestId = ++this.requestCounter
|
|
71
|
+
logger.debug('WorkerClient - sendSingleMessage', requestId, type, payload)
|
|
53
72
|
this.requestCallbacks.set(requestId, (response) => {
|
|
54
73
|
this.requestCallbacks.delete(requestId)
|
|
74
|
+
logger.debug(
|
|
75
|
+
'WorkerClient - sendSingleMessage - response',
|
|
76
|
+
requestId,
|
|
77
|
+
response,
|
|
78
|
+
)
|
|
55
79
|
if (response.data) resolve(response.data)
|
|
56
80
|
else if (response.error) reject(response.error)
|
|
81
|
+
else
|
|
82
|
+
logger.warn(
|
|
83
|
+
'WorkerClient - sendSingleMessage - malformed response',
|
|
84
|
+
requestId,
|
|
85
|
+
response,
|
|
86
|
+
)
|
|
57
87
|
})
|
|
58
88
|
this.worker.postMessage({ type, payload, requestId })
|
|
59
89
|
})
|
|
@@ -95,7 +125,7 @@ export class WorkerClient {
|
|
|
95
125
|
onEnd: () => void = () => {},
|
|
96
126
|
): CancelFunction {
|
|
97
127
|
const requestId = ++this.requestCounter
|
|
98
|
-
|
|
128
|
+
logger.debug('WorkerClient - rpcStream', requestId, module, method, body)
|
|
99
129
|
let unsubscribe: (value: void) => void = () => {}
|
|
100
130
|
let isSubscribed = false
|
|
101
131
|
|
|
@@ -177,6 +207,7 @@ export class WorkerClient {
|
|
|
177
207
|
method: string,
|
|
178
208
|
body: JSONValue,
|
|
179
209
|
): Promise<Response> {
|
|
210
|
+
logger.debug('WorkerClient - rpcSingle', module, method, body)
|
|
180
211
|
return new Promise((resolve, reject) => {
|
|
181
212
|
this.rpcStream<Response>(module, method, body, resolve, reject)
|
|
182
213
|
})
|
|
@@ -187,4 +218,12 @@ export class WorkerClient {
|
|
|
187
218
|
this.initPromise = null
|
|
188
219
|
this.requestCallbacks.clear()
|
|
189
220
|
}
|
|
221
|
+
|
|
222
|
+
// For Testing
|
|
223
|
+
_getRequestCounter() {
|
|
224
|
+
return this.requestCounter
|
|
225
|
+
}
|
|
226
|
+
_getRequestCallbackMap() {
|
|
227
|
+
return this.requestCallbacks
|
|
228
|
+
}
|
|
190
229
|
}
|
package/src/worker/worker.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
// Web Worker for fedimint-client-wasm to run in the browser
|
|
2
2
|
|
|
3
3
|
// HACK: Fixes vitest browser runner
|
|
4
|
+
// TODO: remove once https://github.com/vitest-dev/vitest/pull/6569 lands in a release
|
|
4
5
|
globalThis.__vitest_browser_runner__ = { wrapDynamicImport: (foo) => foo() }
|
|
5
6
|
|
|
7
|
+
// dynamically imported Constructor for WasmClient
|
|
6
8
|
let WasmClient = null
|
|
9
|
+
// client instance
|
|
7
10
|
let client = null
|
|
8
11
|
|
|
9
12
|
const streamCancelMap = new Map()
|
|
@@ -12,74 +15,82 @@ const handleFree = (requestId) => {
|
|
|
12
15
|
streamCancelMap.delete(requestId)
|
|
13
16
|
}
|
|
14
17
|
|
|
18
|
+
console.log('Worker - init')
|
|
19
|
+
|
|
15
20
|
self.onmessage = async (event) => {
|
|
16
21
|
const { type, payload, requestId } = event.data
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
data: { success: !!client },
|
|
27
|
-
requestId,
|
|
28
|
-
})
|
|
29
|
-
} else if (type === 'join') {
|
|
30
|
-
const { inviteCode, clientName: joinClientName } = payload
|
|
31
|
-
try {
|
|
32
|
-
client = await WasmClient.join_federation(joinClientName, inviteCode)
|
|
23
|
+
try {
|
|
24
|
+
if (type === 'init') {
|
|
25
|
+
WasmClient = (await import('@fedimint/fedimint-client-wasm-bundler'))
|
|
26
|
+
.WasmClient
|
|
27
|
+
self.postMessage({ type: 'initialized', data: {}, requestId })
|
|
28
|
+
} else if (type === 'open') {
|
|
29
|
+
const { clientName } = payload
|
|
30
|
+
client = (await WasmClient.open(clientName)) || null
|
|
33
31
|
self.postMessage({
|
|
34
|
-
type: '
|
|
32
|
+
type: 'open',
|
|
35
33
|
data: { success: !!client },
|
|
36
34
|
requestId,
|
|
37
35
|
})
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
} else if (type === 'join') {
|
|
37
|
+
const { inviteCode, clientName: joinClientName } = payload
|
|
38
|
+
try {
|
|
39
|
+
client = await WasmClient.join_federation(joinClientName, inviteCode)
|
|
40
|
+
self.postMessage({
|
|
41
|
+
type: 'join',
|
|
42
|
+
data: { success: !!client },
|
|
43
|
+
requestId,
|
|
44
|
+
})
|
|
45
|
+
} catch (e) {
|
|
46
|
+
self.postMessage({ type: 'error', error: e.message, requestId })
|
|
47
|
+
}
|
|
48
|
+
} else if (type === 'rpc') {
|
|
49
|
+
const { module, method, body } = payload
|
|
50
|
+
console.log('RPC received', module, method, body)
|
|
51
|
+
if (!client) {
|
|
52
|
+
self.postMessage({
|
|
53
|
+
type: 'error',
|
|
54
|
+
error: 'WasmClient not initialized',
|
|
55
|
+
requestId,
|
|
56
|
+
})
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
const rpcHandle = await client.rpc(
|
|
60
|
+
module,
|
|
61
|
+
method,
|
|
62
|
+
JSON.stringify(body),
|
|
63
|
+
(res) => {
|
|
64
|
+
console.log('RPC response', requestId, res)
|
|
65
|
+
const data = JSON.parse(res)
|
|
66
|
+
self.postMessage({ type: 'rpcResponse', requestId, ...data })
|
|
67
|
+
|
|
68
|
+
if (data.end !== undefined) {
|
|
69
|
+
// Handle stream ending
|
|
70
|
+
const handle = streamCancelMap.get(requestId)
|
|
71
|
+
handle?.free()
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
)
|
|
75
|
+
streamCancelMap.set(requestId, rpcHandle)
|
|
76
|
+
} else if (type === 'unsubscribe') {
|
|
77
|
+
const rpcHandle = streamCancelMap.get(requestId)
|
|
78
|
+
if (rpcHandle) {
|
|
79
|
+
rpcHandle.cancel()
|
|
80
|
+
rpcHandle.free()
|
|
81
|
+
streamCancelMap.delete(requestId)
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
45
84
|
self.postMessage({
|
|
46
85
|
type: 'error',
|
|
47
|
-
error: '
|
|
86
|
+
error: 'Unknown message type',
|
|
48
87
|
requestId,
|
|
49
88
|
})
|
|
50
|
-
return
|
|
51
|
-
}
|
|
52
|
-
const rpcHandle = await client.rpc(
|
|
53
|
-
module,
|
|
54
|
-
method,
|
|
55
|
-
JSON.stringify(body),
|
|
56
|
-
(res) => {
|
|
57
|
-
console.log('RPC response', requestId, res)
|
|
58
|
-
const data = JSON.parse(res)
|
|
59
|
-
self.postMessage({ type: 'rpcResponse', requestId, ...data })
|
|
60
|
-
|
|
61
|
-
if (data.end !== undefined) {
|
|
62
|
-
// Handle stream ending
|
|
63
|
-
const handle = streamCancelMap.get(requestId)
|
|
64
|
-
handle?.free()
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
)
|
|
68
|
-
streamCancelMap.set(requestId, rpcHandle)
|
|
69
|
-
} else if (type === 'unsubscribe') {
|
|
70
|
-
const rpcHandle = streamCancelMap.get(requestId)
|
|
71
|
-
if (rpcHandle) {
|
|
72
|
-
rpcHandle.cancel()
|
|
73
|
-
rpcHandle.free()
|
|
74
|
-
streamCancelMap.delete(requestId)
|
|
75
89
|
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
error: 'Unknown message type',
|
|
80
|
-
requestId,
|
|
81
|
-
})
|
|
90
|
+
} catch (e) {
|
|
91
|
+
console.error('ERROR', e)
|
|
92
|
+
self.postMessage({ type: 'error', error: e, requestId })
|
|
82
93
|
}
|
|
83
94
|
}
|
|
84
95
|
|
|
85
|
-
self.postMessage({ type: 'init', data: {} })
|
|
96
|
+
// self.postMessage({ type: 'init', data: {} })
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { expect } from 'vitest'
|
|
2
|
+
import { JSONObject } from '../types/wallet'
|
|
3
|
+
import { TESTING_INVITE } from '../test/TestingService'
|
|
4
|
+
import { workerTest } from '../test/setupTests'
|
|
5
|
+
|
|
6
|
+
// Waits for a message of a given type from the worker
|
|
7
|
+
const waitForWorkerResponse = (
|
|
8
|
+
worker: Worker,
|
|
9
|
+
messageType: string,
|
|
10
|
+
): Promise<JSONObject> => {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
worker.onmessage = (event) => {
|
|
13
|
+
if (event.data.type === messageType) {
|
|
14
|
+
resolve(event.data)
|
|
15
|
+
} else if (event.data.type === 'error') {
|
|
16
|
+
reject(event.data.error)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
worker.onerror = (error) => {
|
|
20
|
+
reject(error.message)
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
workerTest(
|
|
26
|
+
'should initialize WasmClient on init message',
|
|
27
|
+
async ({ worker }) => {
|
|
28
|
+
worker.postMessage({ type: 'init', requestId: 1 })
|
|
29
|
+
const response = await waitForWorkerResponse(worker, 'initialized')
|
|
30
|
+
expect(response.data).toEqual({})
|
|
31
|
+
},
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
workerTest(
|
|
35
|
+
'should return false on open for a new client',
|
|
36
|
+
async ({ worker, clientName }) => {
|
|
37
|
+
worker.postMessage({ type: 'init', requestId: 1 })
|
|
38
|
+
await waitForWorkerResponse(worker, 'initialized')
|
|
39
|
+
|
|
40
|
+
worker.postMessage({
|
|
41
|
+
type: 'open',
|
|
42
|
+
requestId: 2,
|
|
43
|
+
payload: { clientName },
|
|
44
|
+
})
|
|
45
|
+
const response = await waitForWorkerResponse(worker, 'open')
|
|
46
|
+
expect(response.data).toEqual({ success: false })
|
|
47
|
+
},
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
workerTest(
|
|
51
|
+
'should error on fake federation invitation',
|
|
52
|
+
async ({ worker, clientName }) => {
|
|
53
|
+
worker.postMessage({ type: 'init', requestId: 1 })
|
|
54
|
+
await waitForWorkerResponse(worker, 'initialized')
|
|
55
|
+
|
|
56
|
+
worker.postMessage({
|
|
57
|
+
type: 'join',
|
|
58
|
+
requestId: 2,
|
|
59
|
+
payload: { inviteCode: 'fakefederationinvitation', clientName },
|
|
60
|
+
})
|
|
61
|
+
try {
|
|
62
|
+
await waitForWorkerResponse(worker, 'open')
|
|
63
|
+
expect.unreachable()
|
|
64
|
+
} catch (e) {
|
|
65
|
+
expect(e).toBe('parsing failed')
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
workerTest(
|
|
71
|
+
'should handle joining a federation',
|
|
72
|
+
async ({ worker, clientName }) => {
|
|
73
|
+
worker.postMessage({ type: 'init', requestId: 1 })
|
|
74
|
+
await waitForWorkerResponse(worker, 'initialized')
|
|
75
|
+
|
|
76
|
+
worker.postMessage({
|
|
77
|
+
type: 'join',
|
|
78
|
+
requestId: 2,
|
|
79
|
+
payload: { inviteCode: TESTING_INVITE, clientName },
|
|
80
|
+
})
|
|
81
|
+
const response = await waitForWorkerResponse(worker, 'join')
|
|
82
|
+
expect(response.data).toEqual({ success: true })
|
|
83
|
+
},
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
workerTest('should handle unknown message type', async ({ worker }) => {
|
|
87
|
+
worker.postMessage({ type: 'unknown', requestId: 2 })
|
|
88
|
+
const response = await waitForWorkerResponse(worker, 'error')
|
|
89
|
+
expect(response.error).toBe('Unknown message type')
|
|
90
|
+
})
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/* tslint:disable */
|
|
2
|
-
/* eslint-disable */
|
|
3
|
-
/**
|
|
4
|
-
*/
|
|
5
|
-
export class RpcHandle {
|
|
6
|
-
free(): void
|
|
7
|
-
/**
|
|
8
|
-
*/
|
|
9
|
-
cancel(): void
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
*/
|
|
13
|
-
export class WasmClient {
|
|
14
|
-
free(): void
|
|
15
|
-
/**
|
|
16
|
-
* Open fedimint client with already joined federation.
|
|
17
|
-
*
|
|
18
|
-
* After you have joined a federation, you can reopen the fedimint client
|
|
19
|
-
* with same client_name. Opening client with same name at same time is
|
|
20
|
-
* not supported. You can close the current client by calling
|
|
21
|
-
* `client.free()`. NOTE: The client will remain active until all the
|
|
22
|
-
* running rpc calls have finished.
|
|
23
|
-
* @param {string} client_name
|
|
24
|
-
* @returns {Promise<WasmClient | undefined>}
|
|
25
|
-
*/
|
|
26
|
-
static open(client_name: string): Promise<WasmClient | undefined>
|
|
27
|
-
/**
|
|
28
|
-
* Open a fedimint client by join a federation.
|
|
29
|
-
* @param {string} client_name
|
|
30
|
-
* @param {string} invite_code
|
|
31
|
-
* @returns {Promise<WasmClient>}
|
|
32
|
-
*/
|
|
33
|
-
static join_federation(
|
|
34
|
-
client_name: string,
|
|
35
|
-
invite_code: string,
|
|
36
|
-
): Promise<WasmClient>
|
|
37
|
-
/**
|
|
38
|
-
* Call a fedimint client rpc the responses are returned using `cb`
|
|
39
|
-
* callback. Each rpc call *can* return multiple responses by calling
|
|
40
|
-
* `cb` multiple times. The returned RpcHandle can be used to cancel the
|
|
41
|
-
* operation.
|
|
42
|
-
* @param {string} module
|
|
43
|
-
* @param {string} method
|
|
44
|
-
* @param {string} payload
|
|
45
|
-
* @param {Function} cb
|
|
46
|
-
* @returns {RpcHandle}
|
|
47
|
-
*/
|
|
48
|
-
rpc(module: string, method: string, payload: string, cb: Function): RpcHandle
|
|
49
|
-
}
|