@@ -10,9 +10,11 @@ import type {
10
10
} from './APITypes' ;
11
11
12
12
const DEFAULT_SERVER = process . env . WOKWI_CLI_SERVER ?? 'wss://wokwi.com/api/ws/beta' ;
13
+ const retryDelays = [ 1000 , 2000 , 5000 , 10000 , 20000 ] ;
13
14
14
15
export class APIClient {
15
- private readonly socket : WebSocket ;
16
+ private socket : WebSocket ;
17
+ private connectionAttempts = 0 ;
16
18
private lastId = 0 ;
17
19
private _running = false ;
18
20
private _lastNanos = 0 ;
@@ -27,21 +29,62 @@ export class APIClient {
27
29
onEvent ?: ( event : APIEvent ) => void ;
28
30
29
31
constructor ( readonly token : string , readonly server = DEFAULT_SERVER ) {
30
- this . socket = new WebSocket ( server , { headers : { Authorization : `Bearer ${ token } ` } } ) ;
31
- this . socket . addEventListener ( 'message' , ( { data } ) => {
32
- if ( typeof data === 'string' ) {
33
- const message = JSON . parse ( data ) ;
34
- this . processMessage ( message ) ;
35
- } else {
36
- console . error ( 'Unsupported binary message' ) ;
37
- }
38
- } ) ;
39
- this . connected = new Promise ( ( resolve , reject ) => {
32
+ this . socket = this . createSocket ( token , server ) ;
33
+ this . connected = this . connectSocket ( this . socket ) ;
34
+ }
35
+
36
+ private createSocket ( token : string , server : string ) {
37
+ return new WebSocket ( server , { headers : { Authorization : `Bearer ${ token } ` } } ) ;
38
+ }
39
+
40
+ private async connectSocket ( socket : WebSocket ) {
41
+ await new Promise ( ( resolve , reject ) => {
42
+ socket . addEventListener ( 'message' , ( { data } ) => {
43
+ if ( typeof data === 'string' ) {
44
+ const message = JSON . parse ( data ) ;
45
+ this . processMessage ( message ) ;
46
+ } else {
47
+ console . error ( 'Unsupported binary message' ) ;
48
+ }
49
+ } ) ;
40
50
this . socket . addEventListener ( 'open' , resolve ) ;
41
- this . socket . addEventListener ( 'error' , reject ) ;
51
+ this . socket . on ( 'unexpected-response' , ( req , res ) => {
52
+ this . socket . close ( ) ;
53
+ const ServiceUnavailable = 503 ;
54
+ if ( res . statusCode === ServiceUnavailable ) {
55
+ console . warn (
56
+ `Connection to ${ this . server } failed: ${ res . statusMessage ?? '' } (${ res . statusCode } ).`
57
+ ) ;
58
+ resolve ( this . retryConnection ( ) ) ;
59
+ } else {
60
+ reject (
61
+ new Error (
62
+ `Error connecting to ${ this . server } : ${ res . statusCode } ${ res . statusMessage ?? '' } `
63
+ )
64
+ ) ;
65
+ }
66
+ } ) ;
67
+ this . socket . addEventListener ( 'error' , ( event ) => {
68
+ reject ( new Error ( `Error connecting to ${ this . server } : ${ event . message } ` ) ) ;
69
+ } ) ;
42
70
} ) ;
43
71
}
44
72
73
+ private async retryConnection ( ) {
74
+ const delay = retryDelays [ this . connectionAttempts ++ ] ;
75
+ if ( delay == null ) {
76
+ throw new Error ( `Failed to connect to ${ this . server } . Giving up.` ) ;
77
+ }
78
+
79
+ console . log ( `Will retry in ${ delay } ms...` ) ;
80
+
81
+ await new Promise ( ( resolve ) => setTimeout ( resolve , delay ) ) ;
82
+
83
+ console . log ( `Retrying connection to ${ this . server } ...` ) ;
84
+ this . socket = this . createSocket ( this . token , this . server ) ;
85
+ await this . connectSocket ( this . socket ) ;
86
+ }
87
+
45
88
async fileUpload ( name : string , content : string | ArrayBuffer ) {
46
89
if ( typeof content === 'string' ) {
47
90
return await this . sendCommand ( 'file:upload' , { name, text : content } ) ;
0 commit comments