@@ -87,6 +87,16 @@ export async function loginWithHiveAuth(
8787 const key = generateKey ( ) ;
8888 let uuid : string | null = null ;
8989 let authTimeout : ReturnType < typeof setTimeout > | null = null ;
90+ let settled = false ;
91+
92+ // Create challenge once to ensure consistent timestamps between auth_req and QR data
93+ const challenge : HiveAuthChallenge = {
94+ key_type : 'posting' ,
95+ challenge : JSON . stringify ( {
96+ login : true ,
97+ ts : Date . now ( ) ,
98+ } ) ,
99+ } ;
90100
91101 const cleanup = ( ) => {
92102 if ( authTimeout ) {
@@ -98,16 +108,22 @@ export async function loginWithHiveAuth(
98108 }
99109 } ;
100110
101- ws . onopen = ( ) => {
102- // Send auth request
103- const challenge : HiveAuthChallenge = {
104- key_type : 'posting' ,
105- challenge : JSON . stringify ( {
106- login : true ,
107- ts : Date . now ( ) ,
108- } ) ,
109- } ;
111+ const safeReject = ( error : Error ) => {
112+ if ( ! settled ) {
113+ settled = true ;
114+ reject ( error ) ;
115+ }
116+ } ;
110117
118+ const safeResolve = ( ) => {
119+ if ( ! settled ) {
120+ settled = true ;
121+ resolve ( ) ;
122+ }
123+ } ;
124+
125+ ws . onopen = ( ) => {
126+ // Send auth request using the pre-created challenge
111127 const authReq = {
112128 cmd : 'auth_req' ,
113129 account : username ,
@@ -131,13 +147,7 @@ export async function loginWithHiveAuth(
131147 case 'auth_wait' :
132148 if ( msg . uuid ) {
133149 uuid = msg . uuid ;
134- const challenge : HiveAuthChallenge = {
135- key_type : 'posting' ,
136- challenge : JSON . stringify ( {
137- login : true ,
138- ts : Date . now ( ) ,
139- } ) ,
140- } ;
150+ // Reuse the same challenge for QR data
141151 const qrData = generateQRData ( username , uuid , key , challenge ) ;
142152 callbacks . onQRCode ?.( qrData ) ;
143153 callbacks . onWaiting ?.( ) ;
@@ -154,33 +164,33 @@ export async function loginWithHiveAuth(
154164 } ;
155165 callbacks . onSuccess ?.( session ) ;
156166 cleanup ( ) ;
157- resolve ( ) ;
167+ safeResolve ( ) ;
158168 }
159169 break ;
160170
161171 case 'auth_nack' :
162172 callbacks . onError ?.( 'Authentication rejected' ) ;
163173 cleanup ( ) ;
164- reject ( new Error ( 'Authentication rejected' ) ) ;
174+ safeReject ( new Error ( 'Authentication rejected' ) ) ;
165175 break ;
166176
167177 case 'auth_err' :
168178 callbacks . onError ?.( msg . error || 'Authentication error' ) ;
169179 cleanup ( ) ;
170- reject ( new Error ( msg . error || 'Authentication error' ) ) ;
180+ safeReject ( new Error ( msg . error || 'Authentication error' ) ) ;
171181 break ;
172182 }
173183 } catch ( error ) {
174184 callbacks . onError ?.( 'Failed to parse response' ) ;
175185 cleanup ( ) ;
176- reject ( error ) ;
186+ safeReject ( error instanceof Error ? error : new Error ( 'Failed to parse response' ) ) ;
177187 }
178188 } ;
179189
180190 ws . onerror = ( ) => {
181191 callbacks . onError ?.( 'WebSocket connection error' ) ;
182192 cleanup ( ) ;
183- reject ( new Error ( 'WebSocket connection error' ) ) ;
193+ safeReject ( new Error ( 'WebSocket connection error' ) ) ;
184194 } ;
185195
186196 ws . onclose = ( ) => {
@@ -189,14 +199,19 @@ export async function loginWithHiveAuth(
189199 clearTimeout ( authTimeout ) ;
190200 authTimeout = null ;
191201 }
202+ // Reject if the Promise hasn't been settled yet
203+ if ( ! settled ) {
204+ callbacks . onError ?.( 'WebSocket closed before authentication completed' ) ;
205+ safeReject ( new Error ( 'WebSocket closed before authentication completed' ) ) ;
206+ }
192207 } ;
193208
194209 // Timeout after 5 minutes
195210 authTimeout = setTimeout ( ( ) => {
196211 if ( ws . readyState === WebSocket . OPEN ) {
197212 callbacks . onError ?.( 'Authentication timeout' ) ;
198213 cleanup ( ) ;
199- reject ( new Error ( 'Authentication timeout' ) ) ;
214+ safeReject ( new Error ( 'Authentication timeout' ) ) ;
200215 }
201216 } , 5 * 60 * 1000 ) ;
202217 } ) ;
@@ -213,6 +228,7 @@ export async function broadcastWithHiveAuth(
213228 return new Promise ( ( resolve , reject ) => {
214229 const ws = new WebSocket ( HIVEAUTH_API ) ;
215230 let signTimeout : ReturnType < typeof setTimeout > | null = null ;
231+ let settled = false ;
216232
217233 const cleanup = ( ) => {
218234 if ( signTimeout ) {
@@ -224,6 +240,20 @@ export async function broadcastWithHiveAuth(
224240 }
225241 } ;
226242
243+ const safeReject = ( error : Error ) => {
244+ if ( ! settled ) {
245+ settled = true ;
246+ reject ( error ) ;
247+ }
248+ } ;
249+
250+ const safeResolve = ( ) => {
251+ if ( ! settled ) {
252+ settled = true ;
253+ resolve ( ) ;
254+ }
255+ } ;
256+
227257 ws . onopen = ( ) => {
228258 const signReq = {
229259 cmd : 'sign_req' ,
@@ -252,32 +282,32 @@ export async function broadcastWithHiveAuth(
252282 case 'sign_ack' :
253283 callbacks ?. onSuccess ?.( msg . data ) ;
254284 cleanup ( ) ;
255- resolve ( ) ;
285+ safeResolve ( ) ;
256286 break ;
257287
258288 case 'sign_nack' :
259289 callbacks ?. onError ?.( 'Transaction rejected' ) ;
260290 cleanup ( ) ;
261- reject ( new Error ( 'Transaction rejected' ) ) ;
291+ safeReject ( new Error ( 'Transaction rejected' ) ) ;
262292 break ;
263293
264294 case 'sign_err' :
265295 callbacks ?. onError ?.( msg . error || 'Signing error' ) ;
266296 cleanup ( ) ;
267- reject ( new Error ( msg . error || 'Signing error' ) ) ;
297+ safeReject ( new Error ( msg . error || 'Signing error' ) ) ;
268298 break ;
269299 }
270300 } catch ( error ) {
271301 callbacks ?. onError ?.( 'Failed to parse response' ) ;
272302 cleanup ( ) ;
273- reject ( error ) ;
303+ safeReject ( error instanceof Error ? error : new Error ( 'Failed to parse response' ) ) ;
274304 }
275305 } ;
276306
277307 ws . onerror = ( ) => {
278308 callbacks ?. onError ?.( 'WebSocket connection error' ) ;
279309 cleanup ( ) ;
280- reject ( new Error ( 'WebSocket connection error' ) ) ;
310+ safeReject ( new Error ( 'WebSocket connection error' ) ) ;
281311 } ;
282312
283313 ws . onclose = ( ) => {
@@ -286,14 +316,19 @@ export async function broadcastWithHiveAuth(
286316 clearTimeout ( signTimeout ) ;
287317 signTimeout = null ;
288318 }
319+ // Reject if the Promise hasn't been settled yet
320+ if ( ! settled ) {
321+ callbacks ?. onError ?.( 'WebSocket closed before signing completed' ) ;
322+ safeReject ( new Error ( 'WebSocket closed before signing completed' ) ) ;
323+ }
289324 } ;
290325
291326 // Timeout after 2 minutes for signing
292327 signTimeout = setTimeout ( ( ) => {
293328 if ( ws . readyState === WebSocket . OPEN ) {
294329 callbacks ?. onError ?.( 'Signing timeout' ) ;
295330 cleanup ( ) ;
296- reject ( new Error ( 'Signing timeout' ) ) ;
331+ safeReject ( new Error ( 'Signing timeout' ) ) ;
297332 }
298333 } , 2 * 60 * 1000 ) ;
299334 } ) ;
0 commit comments