@fedimint/core-web 0.0.0-20250617190659

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.
Files changed (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +16 -0
  3. package/dist/dts/FedimintWallet.d.ts +114 -0
  4. package/dist/dts/FedimintWallet.d.ts.map +1 -0
  5. package/dist/dts/index.d.ts +3 -0
  6. package/dist/dts/index.d.ts.map +1 -0
  7. package/dist/dts/services/BalanceService.d.ts +15 -0
  8. package/dist/dts/services/BalanceService.d.ts.map +1 -0
  9. package/dist/dts/services/FederationService.d.ts +11 -0
  10. package/dist/dts/services/FederationService.d.ts.map +1 -0
  11. package/dist/dts/services/LightningService.d.ts +47 -0
  12. package/dist/dts/services/LightningService.d.ts.map +1 -0
  13. package/dist/dts/services/MintService.d.ts +23 -0
  14. package/dist/dts/services/MintService.d.ts.map +1 -0
  15. package/dist/dts/services/RecoveryService.d.ts +13 -0
  16. package/dist/dts/services/RecoveryService.d.ts.map +1 -0
  17. package/dist/dts/services/WalletService.d.ts +8 -0
  18. package/dist/dts/services/WalletService.d.ts.map +1 -0
  19. package/dist/dts/services/index.d.ts +7 -0
  20. package/dist/dts/services/index.d.ts.map +1 -0
  21. package/dist/dts/types/index.d.ts +4 -0
  22. package/dist/dts/types/index.d.ts.map +1 -0
  23. package/dist/dts/types/utils.d.ts +23 -0
  24. package/dist/dts/types/utils.d.ts.map +1 -0
  25. package/dist/dts/types/wallet.d.ts +102 -0
  26. package/dist/dts/types/wallet.d.ts.map +1 -0
  27. package/dist/dts/types/worker.d.ts +4 -0
  28. package/dist/dts/types/worker.d.ts.map +1 -0
  29. package/dist/dts/utils/logger.d.ts +17 -0
  30. package/dist/dts/utils/logger.d.ts.map +1 -0
  31. package/dist/dts/worker/WorkerClient.d.ts +44 -0
  32. package/dist/dts/worker/WorkerClient.d.ts.map +1 -0
  33. package/dist/dts/worker/index.d.ts +2 -0
  34. package/dist/dts/worker/index.d.ts.map +1 -0
  35. package/dist/index.d.ts +392 -0
  36. package/dist/index.js +2 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/worker.js +2 -0
  39. package/dist/worker.js.map +1 -0
  40. package/package.json +43 -0
  41. package/src/FedimintWallet.test.ts +73 -0
  42. package/src/FedimintWallet.ts +215 -0
  43. package/src/index.ts +2 -0
  44. package/src/services/BalanceService.test.ts +50 -0
  45. package/src/services/BalanceService.ts +29 -0
  46. package/src/services/FederationService.test.ts +58 -0
  47. package/src/services/FederationService.ts +24 -0
  48. package/src/services/LightningService.test.ts +208 -0
  49. package/src/services/LightningService.ts +274 -0
  50. package/src/services/MintService.test.ts +74 -0
  51. package/src/services/MintService.ts +129 -0
  52. package/src/services/RecoveryService.ts +28 -0
  53. package/src/services/WalletService.test.ts +24 -0
  54. package/src/services/WalletService.ts +10 -0
  55. package/src/services/index.ts +6 -0
  56. package/src/test/TestFedimintWallet.ts +26 -0
  57. package/src/test/TestingService.ts +79 -0
  58. package/src/test/crypto.ts +44 -0
  59. package/src/test/fixtures.test.ts +18 -0
  60. package/src/test/fixtures.ts +70 -0
  61. package/src/types/index.ts +3 -0
  62. package/src/types/utils.ts +29 -0
  63. package/src/types/wallet.ts +141 -0
  64. package/src/types/worker.ts +16 -0
  65. package/src/utils/logger.ts +61 -0
  66. package/src/worker/WorkerClient.test.ts +6 -0
  67. package/src/worker/WorkerClient.ts +236 -0
  68. package/src/worker/index.ts +1 -0
  69. package/src/worker/worker.js +167 -0
  70. package/src/worker/worker.test.ts +90 -0
@@ -0,0 +1,18 @@
1
+ import { expect } from 'vitest'
2
+ import { walletTest } from './fixtures'
3
+
4
+ walletTest('Fund wallet 1', async ({ fundedWallet }) => {
5
+ expect(fundedWallet).toBeDefined()
6
+ })
7
+
8
+ walletTest('Fund wallet 2', async ({ fundedWallet }) => {
9
+ expect(fundedWallet).toBeDefined()
10
+ })
11
+
12
+ walletTest('Fund wallet 3', async ({ fundedWallet }) => {
13
+ expect(fundedWallet).toBeDefined()
14
+ })
15
+
16
+ walletTest('Fund wallet 4', async ({ fundedWallet }) => {
17
+ expect(fundedWallet).toBeDefined()
18
+ })
@@ -0,0 +1,70 @@
1
+ import { expect, test } from 'vitest'
2
+ import { TestFedimintWallet } from './TestFedimintWallet'
3
+ import { WorkerClient } from '../worker/WorkerClient'
4
+
5
+ /**
6
+ * Adds Fixtures for setting up and tearing down a test FedimintWallet instance
7
+ */
8
+ export const walletTest = test.extend<{
9
+ wallet: TestFedimintWallet
10
+ fundedWallet: TestFedimintWallet
11
+ unopenedWallet: TestFedimintWallet
12
+ }>({
13
+ wallet: async ({}, use) => {
14
+ const randomTestingId = Math.random().toString(36).substring(2, 15)
15
+ const wallet = new TestFedimintWallet()
16
+ expect(wallet).toBeDefined()
17
+ const inviteCode = await wallet.testing.getInviteCode()
18
+ await expect(
19
+ wallet.joinFederation(inviteCode, randomTestingId),
20
+ ).resolves.toBe(true)
21
+
22
+ await use(wallet)
23
+
24
+ // clear up browser resources
25
+ await wallet.cleanup()
26
+
27
+ // remove the wallet db
28
+ await new Promise((resolve) => {
29
+ const request = indexedDB.deleteDatabase(randomTestingId)
30
+ request.onsuccess = resolve
31
+ request.onerror = resolve
32
+ request.onblocked = resolve
33
+ })
34
+ },
35
+
36
+ fundedWallet: async ({ wallet }, use) => {
37
+ await wallet.fundWallet(10_000)
38
+ await use(wallet)
39
+ },
40
+ unopenedWallet: async ({}, use) => {
41
+ const wallet = new TestFedimintWallet()
42
+ await wallet.initialize()
43
+ await use(wallet)
44
+ },
45
+ })
46
+
47
+ /**
48
+ * Adds Fixtures for setting up and tearing down a test Worker instance
49
+ */
50
+ export const workerTest = test.extend<{
51
+ worker: Worker
52
+ clientName: string
53
+ workerClient: WorkerClient
54
+ }>({
55
+ worker: async ({}, use) => {
56
+ const worker = new Worker(new URL('../worker/worker.js', import.meta.url), {
57
+ type: 'module',
58
+ })
59
+ await use(worker)
60
+ worker.terminate()
61
+ },
62
+ clientName: async ({}, use) => {
63
+ const randomTestingId = Math.random().toString(36).substring(2, 15)
64
+ await use(randomTestingId)
65
+ },
66
+ workerClient: async ({}, use) => {
67
+ const workerClient = new WorkerClient()
68
+ await use(workerClient)
69
+ },
70
+ })
@@ -0,0 +1,3 @@
1
+ export * from './wallet'
2
+ export * from './utils'
3
+ export * from './worker'
@@ -0,0 +1,29 @@
1
+ type Alias<T> = T & {}
2
+ type Resolve<T> = T & unknown
3
+
4
+ type Seconds = Alias<number>
5
+ type Nanos = Alias<number>
6
+
7
+ type Duration = {
8
+ nanos: Nanos
9
+ secs: Seconds
10
+ }
11
+
12
+ type MSats = Alias<number>
13
+ type Sats = Alias<number>
14
+
15
+ type JSONValue =
16
+ | string
17
+ | number
18
+ | boolean
19
+ | null
20
+ | { [key: string]: JSONValue }
21
+ | JSONValue[]
22
+
23
+ type JSONObject = Record<string, JSONValue>
24
+
25
+ type Result<T, U = string> =
26
+ | { success: true; data?: T }
27
+ | { success: false; error: U }
28
+
29
+ export { Alias, Resolve, Duration, MSats, Sats, JSONValue, JSONObject, Result }
@@ -0,0 +1,141 @@
1
+ import { MSats, Duration, JSONValue, JSONObject } from './utils'
2
+
3
+ const MODULE_KINDS = ['', 'ln', 'mint', 'wallet'] as const
4
+ type ModuleKind = (typeof MODULE_KINDS)[number]
5
+
6
+ // TODO: Define the structure of FederationConfig
7
+ type FederationConfig = JSONObject
8
+
9
+ type GatewayInfo = {
10
+ gateway_id: string
11
+ api: string
12
+ node_pub_key: string
13
+ federation_index: number
14
+ route_hints: RouteHint[]
15
+ fees: FeeToAmount
16
+ }
17
+ type LightningGateway = {
18
+ info: GatewayInfo
19
+ vetted: boolean
20
+ ttl: Duration
21
+ }
22
+
23
+ type RouteHint = {
24
+ // TODO: Define the structure of RouteHint
25
+ }
26
+
27
+ type FeeToAmount = {
28
+ // TODO: Define the structure of FeeToAmount
29
+ }
30
+
31
+ type OutgoingLightningPayment = {
32
+ payment_type: PayType
33
+ contract_id: string
34
+ fee: MSats
35
+ }
36
+
37
+ type PayType = { lightning: string } | { internal: string }
38
+
39
+ type LnPayState =
40
+ | 'created'
41
+ | 'canceled'
42
+ | { funded: { block_height: number } }
43
+ | { waiting_for_refund: { error_reason: string } }
44
+ | 'awaiting_change'
45
+ | { success: { preimage: string } }
46
+ | { refunded: { gateway_error: string } }
47
+ | { unexpected_error: { error_message: string } }
48
+
49
+ type LnReceiveState =
50
+ | 'created'
51
+ | { waiting_for_payment: { invoice: string; timeout: number } }
52
+ | { canceled: { reason: string } }
53
+ | 'funded'
54
+ | 'awaiting_funds'
55
+ | 'claimed'
56
+
57
+ type CreateBolt11Response = {
58
+ operation_id: string
59
+ invoice: string
60
+ }
61
+
62
+ type StreamError = {
63
+ error: string
64
+ data: never
65
+ end: never
66
+ }
67
+
68
+ type StreamSuccess<T extends JSONValue> = {
69
+ data: T
70
+ error: never
71
+ end: never
72
+ }
73
+
74
+ type StreamEnd = {
75
+ end: string
76
+ data: never
77
+ error: never
78
+ }
79
+
80
+ type StreamResult<T extends JSONValue> =
81
+ | StreamSuccess<T>
82
+ | StreamError
83
+ | StreamEnd
84
+
85
+ type CancelFunction = () => void
86
+
87
+ type ReissueExternalNotesState = 'Created' | 'Issuing' | 'Done'
88
+ // | { Failed: { error: string } }
89
+
90
+ type MintSpendNotesResponse = Array<string>
91
+
92
+ type SpendNotesState =
93
+ | 'Created'
94
+ | 'UserCanceledProcessing'
95
+ | 'UserCanceledSuccess'
96
+ | 'UserCanceledFailure'
97
+ | 'Success'
98
+ | 'Refunded'
99
+
100
+ type TxOutputSummary = {
101
+ outpoint: {
102
+ txid: string
103
+ vout: number
104
+ }
105
+ amount: number
106
+ }
107
+
108
+ type WalletSummary = {
109
+ spendable_utxos: TxOutputSummary[]
110
+ unsigned_peg_out_txos: TxOutputSummary[]
111
+ unsigned_change_utxos: TxOutputSummary[]
112
+ unconfirmed_peg_out_txos: TxOutputSummary[]
113
+ unconfirmed_change_utxos: TxOutputSummary[]
114
+ }
115
+
116
+ /** Keys are powers of 2 */
117
+ type NoteCountByDenomination = Record<number, number>
118
+
119
+ export {
120
+ LightningGateway,
121
+ FederationConfig,
122
+ RouteHint,
123
+ FeeToAmount,
124
+ OutgoingLightningPayment,
125
+ PayType,
126
+ LnPayState,
127
+ LnReceiveState,
128
+ CreateBolt11Response,
129
+ GatewayInfo,
130
+ StreamError,
131
+ StreamSuccess,
132
+ StreamResult,
133
+ ModuleKind,
134
+ CancelFunction,
135
+ ReissueExternalNotesState,
136
+ MintSpendNotesResponse,
137
+ SpendNotesState,
138
+ WalletSummary,
139
+ TxOutputSummary,
140
+ NoteCountByDenomination,
141
+ }
@@ -0,0 +1,16 @@
1
+ const WorkerMessageTypes = [
2
+ 'init',
3
+ 'initialized',
4
+ 'rpc',
5
+ 'log',
6
+ 'open',
7
+ 'join',
8
+ 'error',
9
+ 'unsubscribe',
10
+ 'cleanup',
11
+ 'parseInviteCode',
12
+ 'parseBolt11Invoice',
13
+ 'previewFederation',
14
+ ] as const
15
+
16
+ export type WorkerMessageType = (typeof WorkerMessageTypes)[number]
@@ -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()
@@ -0,0 +1,6 @@
1
+ import { expect } from 'vitest'
2
+ import { workerTest } from '../test/fixtures'
3
+
4
+ workerTest('should initialize', async ({ workerClient }) => {
5
+ expect(workerClient).toBeDefined()
6
+ })
@@ -0,0 +1,236 @@
1
+ import type {
2
+ CancelFunction,
3
+ JSONValue,
4
+ ModuleKind,
5
+ StreamError,
6
+ StreamResult,
7
+ WorkerMessageType,
8
+ } from '../types'
9
+ import { logger } from '../utils/logger'
10
+
11
+ // Handles communication with the wasm worker
12
+ // TODO: Move rpc stream management to a separate "SubscriptionManager" class
13
+ export class WorkerClient {
14
+ private worker: Worker
15
+ private requestCounter = 0
16
+ private requestCallbacks = new Map<number, (value: any) => void>()
17
+ private initPromise: Promise<boolean> | undefined = undefined
18
+
19
+ constructor() {
20
+ // Must create the URL inside the constructor for vite
21
+ this.worker = new Worker(new URL('./worker.js', import.meta.url), {
22
+ type: 'module',
23
+ })
24
+ this.worker.onmessage = this.handleWorkerMessage.bind(this)
25
+ this.worker.onerror = this.handleWorkerError.bind(this)
26
+ logger.info('WorkerClient instantiated')
27
+ logger.debug('WorkerClient', this.worker)
28
+ }
29
+
30
+ // Idempotent setup - Loads the wasm module
31
+ initialize() {
32
+ if (this.initPromise) return this.initPromise
33
+ this.initPromise = this.sendSingleMessage('init')
34
+ return this.initPromise
35
+ }
36
+
37
+ private handleWorkerLogs(event: MessageEvent) {
38
+ const { type, level, message, ...data } = event.data
39
+ logger.log(level, message, ...data)
40
+ }
41
+
42
+ private handleWorkerError(event: ErrorEvent) {
43
+ logger.error('Worker error', event)
44
+ }
45
+
46
+ private handleWorkerMessage(event: MessageEvent) {
47
+ const { type, requestId, ...data } = event.data
48
+ if (type === 'log') {
49
+ this.handleWorkerLogs(event.data)
50
+ }
51
+ const streamCallback = this.requestCallbacks.get(requestId)
52
+ // TODO: Handle errors... maybe have another callbacks list for errors?
53
+ logger.debug('WorkerClient - handleWorkerMessage', event.data)
54
+ if (streamCallback) {
55
+ streamCallback(data) // {data: something} OR {error: something}
56
+ } else {
57
+ logger.warn(
58
+ 'WorkerClient - handleWorkerMessage - received message with no callback',
59
+ requestId,
60
+ event.data,
61
+ )
62
+ }
63
+ }
64
+
65
+ // TODO: Handle errors... maybe have another callbacks list for errors?
66
+ // TODO: Handle timeouts
67
+ // TODO: Handle multiple errors
68
+
69
+ sendSingleMessage<
70
+ Response extends JSONValue = JSONValue,
71
+ Payload extends JSONValue = JSONValue,
72
+ >(type: WorkerMessageType, payload?: Payload) {
73
+ return new Promise<Response>((resolve, reject) => {
74
+ const requestId = ++this.requestCounter
75
+ logger.debug('WorkerClient - sendSingleMessage', requestId, type, payload)
76
+ this.requestCallbacks.set(
77
+ requestId,
78
+ (response: StreamResult<Response>) => {
79
+ this.requestCallbacks.delete(requestId)
80
+ logger.debug(
81
+ 'WorkerClient - sendSingleMessage - response',
82
+ requestId,
83
+ response,
84
+ )
85
+ if (response.data) resolve(response.data)
86
+ else if (response.error) reject(response.error)
87
+ else
88
+ logger.warn(
89
+ 'WorkerClient - sendSingleMessage - malformed response',
90
+ requestId,
91
+ response,
92
+ )
93
+ },
94
+ )
95
+ this.worker.postMessage({ type, payload, requestId })
96
+ })
97
+ }
98
+
99
+ /**
100
+ * @summary Initiates an RPC stream with the specified module and method.
101
+ *
102
+ * @description
103
+ * This function sets up an RPC stream by sending a request to a worker and
104
+ * handling responses asynchronously. It ensures that unsubscription is handled
105
+ * correctly, even if the unsubscribe function is called before the subscription
106
+ * is fully established, by deferring the unsubscription attempt using `setTimeout`.
107
+ *
108
+ * The function operates in a non-blocking manner, leveraging Promises to manage
109
+ * asynchronous operations and callbacks to handle responses.
110
+ *
111
+ *
112
+ * @template Response - The expected type of the successful response.
113
+ * @template Body - The type of the request body.
114
+ * @param module - The module kind to interact with.
115
+ * @param method - The method name to invoke on the module.
116
+ * @param body - The request payload.
117
+ * @param onSuccess - Callback invoked with the response data on success.
118
+ * @param onError - Callback invoked with error information if an error occurs.
119
+ * @param onEnd - Optional callback invoked when the stream ends.
120
+ * @returns A function that can be called to cancel the subscription.
121
+ *
122
+ */
123
+ rpcStream<
124
+ Response extends JSONValue = JSONValue,
125
+ Body extends JSONValue = JSONValue,
126
+ >(
127
+ module: ModuleKind,
128
+ method: string,
129
+ body: Body,
130
+ onSuccess: (res: Response) => void,
131
+ onError: (res: StreamError['error']) => void,
132
+ onEnd: () => void = () => {},
133
+ ): CancelFunction {
134
+ const requestId = ++this.requestCounter
135
+ logger.debug('WorkerClient - rpcStream', requestId, module, method, body)
136
+ let unsubscribe: (value: void) => void = () => {}
137
+ let isSubscribed = false
138
+
139
+ const unsubscribePromise = new Promise<void>((resolve) => {
140
+ unsubscribe = () => {
141
+ if (isSubscribed) {
142
+ // If already subscribed, resolve immediately to trigger unsubscription
143
+ resolve()
144
+ } else {
145
+ // If not yet subscribed, defer the unsubscribe attempt to the next event loop tick
146
+ // This ensures that subscription setup has time to complete
147
+ setTimeout(() => unsubscribe(), 0)
148
+ }
149
+ }
150
+ })
151
+
152
+ // Initiate the inner RPC stream setup asynchronously
153
+ this._rpcStreamInner(
154
+ requestId,
155
+ module,
156
+ method,
157
+ body,
158
+ onSuccess,
159
+ onError,
160
+ onEnd,
161
+ unsubscribePromise,
162
+ ).then(() => {
163
+ isSubscribed = true
164
+ })
165
+
166
+ return unsubscribe
167
+ }
168
+
169
+ private async _rpcStreamInner<
170
+ Response extends JSONValue = JSONValue,
171
+ Body extends JSONValue = JSONValue,
172
+ >(
173
+ requestId: number,
174
+ module: ModuleKind,
175
+ method: string,
176
+ body: Body,
177
+ onSuccess: (res: Response) => void,
178
+ onError: (res: StreamError['error']) => void,
179
+ onEnd: () => void = () => {},
180
+ unsubscribePromise: Promise<void>,
181
+ // Unsubscribe function
182
+ ) {
183
+ // await this.openPromise
184
+ // if (!this.worker || !this._isOpen)
185
+ // throw new Error('FedimintWallet is not open')
186
+
187
+ this.requestCallbacks.set(requestId, (response: StreamResult<Response>) => {
188
+ if (response.error !== undefined) {
189
+ onError(response.error)
190
+ } else if (response.data !== undefined) {
191
+ onSuccess(response.data)
192
+ } else if (response.end !== undefined) {
193
+ this.requestCallbacks.delete(requestId)
194
+ onEnd()
195
+ }
196
+ })
197
+ this.worker.postMessage({
198
+ type: 'rpc',
199
+ payload: { module, method, body },
200
+ requestId,
201
+ })
202
+
203
+ unsubscribePromise.then(() => {
204
+ this.worker?.postMessage({
205
+ type: 'unsubscribe',
206
+ requestId,
207
+ })
208
+ this.requestCallbacks.delete(requestId)
209
+ })
210
+ }
211
+
212
+ rpcSingle<
213
+ Response extends JSONValue = JSONValue,
214
+ Error extends string = string,
215
+ >(module: ModuleKind, method: string, body: JSONValue) {
216
+ logger.debug('WorkerClient - rpcSingle', module, method, body)
217
+ return new Promise<Response>((resolve, reject) => {
218
+ this.rpcStream<Response>(module, method, body, resolve, reject)
219
+ })
220
+ }
221
+
222
+ async cleanup() {
223
+ await this.sendSingleMessage('cleanup')
224
+ this.requestCounter = 0
225
+ this.initPromise = undefined
226
+ this.requestCallbacks.clear()
227
+ }
228
+
229
+ // For Testing
230
+ _getRequestCounter() {
231
+ return this.requestCounter
232
+ }
233
+ _getRequestCallbackMap() {
234
+ return this.requestCallbacks
235
+ }
236
+ }
@@ -0,0 +1 @@
1
+ export { WorkerClient } from './WorkerClient'