@@ -10,9 +10,11 @@ import type {
1010} from './APITypes' ;
1111
1212const DEFAULT_SERVER = process . env . WOKWI_CLI_SERVER ?? 'wss://wokwi.com/api/ws/beta' ;
13+ const retryDelays = [ 1000 , 2000 , 5000 , 10000 , 20000 ] ;
1314
1415export class APIClient {
15- private readonly socket : WebSocket ;
16+ private socket : WebSocket ;
17+ private connectionAttempts = 0 ;
1618 private lastId = 0 ;
1719 private _running = false ;
1820 private _lastNanos = 0 ;
@@ -27,21 +29,62 @@ export class APIClient {
2729 onEvent ?: ( event : APIEvent ) => void ;
2830
2931 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+ } ) ;
4050 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+ } ) ;
4270 } ) ;
4371 }
4472
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+
4588 async fileUpload ( name : string , content : string | ArrayBuffer ) {
4689 if ( typeof content === 'string' ) {
4790 return await this . sendCommand ( 'file:upload' , { name, text : content } ) ;
0 commit comments