Skip to content

Commit 6458958

Browse files
leaanthonyWails Documentation Agent
andauthored
fix(v2): Reject binding errors with Error objects instead of strings (#5244)
fix: reject bindings with Error objects instead of plain strings (issue #4379) When a Go binding returns an error, the JS runtime was rejecting the promise with a plain string. This broke error handling in libraries like TanStack Query that expect Error instances. Now wraps the error message in a new Error() before rejecting. Co-authored-by: Wails Documentation Agent <agent@wails.local>
1 parent 416749c commit 6458958

4 files changed

Lines changed: 92 additions & 4 deletions

File tree

v2/internal/frontend/runtime/desktop/calls.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,8 @@ export function Callback(incomingMessage) {
178178
delete callbacks[callbackID];
179179

180180
if (message.error) {
181-
callbackData.reject(message.error);
181+
const err = message.error instanceof Error ? message.error : new Error(message.error);
182+
callbackData.reject(err);
182183
} else {
183184
callbackData.resolve(message.result);
184185
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { Call, Callback, callbacks } from './calls'
2+
import { expect, describe, it, beforeAll, vi, afterEach } from 'vitest'
3+
4+
beforeAll(() => {
5+
window.WailsInvoke = vi.fn(() => {})
6+
window.runtime = {
7+
LogDebug: vi.fn(),
8+
}
9+
})
10+
11+
afterEach(() => {
12+
vi.clearAllMocks()
13+
Object.keys(callbacks).forEach(key => delete callbacks[key])
14+
})
15+
16+
describe('Callback', () => {
17+
it('should reject with Error object when binding returns error (issue #4379)', async () => {
18+
const promise = Call('main/App.SetHello', ['test'], 0)
19+
20+
const invokeCall = window.WailsInvoke.mock.calls[0][0]
21+
const payload = JSON.parse(invokeCall.slice(1))
22+
const callbackID = payload.callbackID
23+
24+
Callback(JSON.stringify({
25+
callbackid: callbackID,
26+
error: "some error message"
27+
}))
28+
29+
try {
30+
await promise
31+
expect.unreachable('should have rejected')
32+
} catch (e) {
33+
expect(e).toBeInstanceOf(Error)
34+
expect(e.message).toBe('some error message')
35+
}
36+
})
37+
38+
it('should resolve with result when binding succeeds', async () => {
39+
const promise = Call('main/App.GetValue', [], 0)
40+
41+
const invokeCall = window.WailsInvoke.mock.calls[0][0]
42+
const payload = JSON.parse(invokeCall.slice(1))
43+
const callbackID = payload.callbackID
44+
45+
Callback(JSON.stringify({
46+
callbackid: callbackID,
47+
result: "hello world"
48+
}))
49+
50+
const result = await promise
51+
expect(result).toBe("hello world")
52+
})
53+
54+
it('should reject with Error on timeout', async () => {
55+
const promise = Call('main/App.SlowCall', [], 50)
56+
57+
try {
58+
await promise
59+
expect.unreachable('should have rejected')
60+
} catch (e) {
61+
expect(e).toBeInstanceOf(Error)
62+
expect(e.message).toContain('timed out')
63+
}
64+
})
65+
66+
it('should reject with Error object for ObfuscatedCall errors', async () => {
67+
const promise = window.ObfuscatedCall('abc123', [], 0)
68+
69+
const invokeCall = window.WailsInvoke.mock.calls[0][0]
70+
const payload = JSON.parse(invokeCall.slice(1))
71+
const callbackID = payload.callbackID
72+
73+
Callback(JSON.stringify({
74+
callbackid: callbackID,
75+
error: "obfuscated error"
76+
}))
77+
78+
try {
79+
await promise
80+
expect.unreachable('should have rejected')
81+
} catch (e) {
82+
expect(e).toBeInstanceOf(Error)
83+
expect(e.message).toBe('obfuscated error')
84+
}
85+
})
86+
})

v2/internal/frontend/runtime/runtime_debug_desktop.js

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)