@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,167 @@
1
+ // Web Worker for fedimint-client-wasm to run in the browser
2
+
3
+ // HACK: Fixes vitest browser runner
4
+ // TODO: remove once https://github.com/vitest-dev/vitest/pull/6569 lands in a release
5
+ globalThis.__vitest_browser_runner__ = { wrapDynamicImport: (foo) => foo() }
6
+
7
+ // dynamically imported Constructor for WasmClient
8
+ let WasmClient = null
9
+ // client instance
10
+ let client = null
11
+
12
+ const streamCancelMap = new Map()
13
+
14
+ const handleFree = (requestId) => {
15
+ streamCancelMap.delete(requestId)
16
+ }
17
+
18
+ console.log('Worker - init')
19
+
20
+ /**
21
+ * Type definitions for the worker messages
22
+ *
23
+ * @typedef {import('../types/worker').WorkerMessageType} WorkerMessageType
24
+ * @typedef {{
25
+ * type: WorkerMessageType
26
+ * payload: any
27
+ * requestId: number
28
+ * }} WorkerMessage
29
+ * @param {{data: WorkerMessage}} event
30
+ */
31
+ self.onmessage = async (event) => {
32
+ const { type, payload, requestId } = event.data
33
+
34
+ try {
35
+ if (type === 'init') {
36
+ WasmClient = (await import('@fedimint/fedimint-client-wasm-bundler'))
37
+ .WasmClient
38
+ self.postMessage({ type: 'initialized', data: {}, requestId })
39
+ } else if (type === 'open') {
40
+ const { clientName } = payload
41
+ client = (await WasmClient.open(clientName)) || null
42
+ self.postMessage({
43
+ type: 'open',
44
+ data: { success: !!client },
45
+ requestId,
46
+ })
47
+ } else if (type === 'join') {
48
+ const { inviteCode, clientName: joinClientName } = payload
49
+ try {
50
+ client = await WasmClient.join_federation(joinClientName, inviteCode)
51
+ self.postMessage({
52
+ type: 'join',
53
+ data: { success: !!client },
54
+ requestId,
55
+ })
56
+ } catch (e) {
57
+ self.postMessage({ type: 'error', error: e.message, requestId })
58
+ }
59
+ } else if (type === 'previewFederation') {
60
+ const { inviteCode } = payload
61
+ try {
62
+ client = await WasmClient.preview_federation(inviteCode)
63
+ const parsed = JSON.parse(client)
64
+ self.postMessage({
65
+ type: 'previewFederation',
66
+ data: {
67
+ success: !!client,
68
+ config: parsed.config,
69
+ federation_id: parsed.federation_id,
70
+ },
71
+ requestId,
72
+ })
73
+ } catch (e) {
74
+ self.postMessage({ type: 'error', error: e.message, requestId })
75
+ }
76
+ } else if (type === 'rpc') {
77
+ const { module, method, body } = payload
78
+ console.log('RPC received', module, method, body)
79
+ if (!client) {
80
+ self.postMessage({
81
+ type: 'error',
82
+ error: 'WasmClient not initialized',
83
+ requestId,
84
+ })
85
+ return
86
+ }
87
+ const rpcHandle = await client.rpc(
88
+ module,
89
+ method,
90
+ JSON.stringify(body),
91
+ (res) => {
92
+ console.log('RPC response', requestId, res)
93
+ const data = JSON.parse(res)
94
+ self.postMessage({ type: 'rpcResponse', requestId, ...data })
95
+
96
+ if (data.end !== undefined) {
97
+ // Handle stream ending
98
+ const handle = streamCancelMap.get(requestId)
99
+ handle?.free()
100
+ }
101
+ },
102
+ )
103
+ streamCancelMap.set(requestId, rpcHandle)
104
+ } else if (type === 'unsubscribe') {
105
+ const rpcHandle = streamCancelMap.get(requestId)
106
+ if (rpcHandle) {
107
+ rpcHandle.cancel()
108
+ rpcHandle.free()
109
+ streamCancelMap.delete(requestId)
110
+ }
111
+ } else if (type === 'cleanup') {
112
+ console.log('cleanup message received')
113
+ client?.free()
114
+ self.postMessage({
115
+ type: 'cleanup',
116
+ data: {},
117
+ requestId,
118
+ })
119
+ close()
120
+ } else if (type === 'parseInviteCode') {
121
+ const { inviteCode } = payload
122
+ try {
123
+ const res = WasmClient.parse_invite_code(inviteCode)
124
+ const parsedRes = JSON.parse(res)
125
+ self.postMessage({
126
+ type: 'parseInviteCode',
127
+ data: parsedRes,
128
+ requestId,
129
+ })
130
+ } catch (error) {
131
+ self.postMessage({
132
+ type: 'error',
133
+ error: `Failed to parse invite code: ${error.message}`,
134
+ requestId,
135
+ })
136
+ }
137
+ } else if (type === 'parseBolt11Invoice') {
138
+ const { invoiceStr } = payload
139
+ try {
140
+ const res = WasmClient.parse_bolt11_invoice(invoiceStr)
141
+ const parsedRes = JSON.parse(res)
142
+ self.postMessage({
143
+ type: 'parseBolt11Invoice',
144
+ data: parsedRes,
145
+ requestId,
146
+ })
147
+ } catch (error) {
148
+ self.postMessage({
149
+ type: 'error',
150
+ error: `Failed to parse invoice: ${error.message}`,
151
+ requestId,
152
+ })
153
+ }
154
+ } else {
155
+ self.postMessage({
156
+ type: 'error',
157
+ error: 'Unknown message type',
158
+ requestId,
159
+ })
160
+ }
161
+ } catch (e) {
162
+ console.error('ERROR', e)
163
+ self.postMessage({ type: 'error', error: e, requestId })
164
+ }
165
+ }
166
+
167
+ // self.postMessage({ type: 'init', data: {} })
@@ -0,0 +1,90 @@
1
+ import { expect } from 'vitest'
2
+ import { TESTING_INVITE } from '../test/TestingService'
3
+ import { JSONObject } from '../types'
4
+ import { workerTest } from '../test/fixtures'
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
+ })