2121 */
2222
2323import NativeUnavailableError from "@web-eid.js/errors/NativeUnavailableError" ;
24- import UnknownError from "@web-eid.js/errors/UnknownError" ;
2524import { deserializeError } from "@web-eid.js/utils/errorSerializer" ;
2625import libraryConfig from "@web-eid.js/config" ;
2726
28- import { NativeFailureResponse } from "@web-eid.js/models/message/NativeResponse" ;
2927import { NativeRequest } from "@web-eid.js/models/message/NativeRequest" ;
3028import { Port } from "../../models/Browser/Runtime" ;
3129import calculateJsonSize from "../../shared/utils/calculateJsonSize" ;
3230import config from "../../config" ;
33- import { throwAfterTimeout } from "../../shared/utils/timing" ;
34-
35- type UnwrappedPromise
36- = { resolve : ( value ?: any ) => void ; reject : ( reason ?: any ) => void }
37- | null ;
3831
3932export enum NativeAppState {
4033 UNINITIALIZED ,
@@ -47,21 +40,23 @@ export default class NativeAppService {
4740 public state : NativeAppState = NativeAppState . UNINITIALIZED ;
4841
4942 private port : Port | null = null ;
50- private pending : UnwrappedPromise = null ;
51- private activeConnection : UnwrappedPromise = null ;
5243
5344 async connect ( ) : Promise < { version : string } > {
5445 this . state = NativeAppState . CONNECTING ;
46+ this . port = browser . runtime . connectNative ( config . NATIVE_APP_NAME ) ;
5547
56- this . port = browser . runtime . connectNative ( config . NATIVE_APP_NAME ) ;
5748 this . port . onDisconnect . addListener ( this . disconnectListener . bind ( this ) ) ;
5849
5950 try {
60- const message = await this . nextMessage ( libraryConfig . NATIVE_APP_HANDSHAKE_TIMEOUT ) ;
51+ const message = await this . nextMessage (
52+ libraryConfig . NATIVE_APP_HANDSHAKE_TIMEOUT ,
53+ new NativeUnavailableError (
54+ `native application handshake timeout, ${ libraryConfig . NATIVE_APP_HANDSHAKE_TIMEOUT } ms`
55+ ) ,
56+ ) ;
6157
6258 if ( message . version ) {
6359 this . state = NativeAppState . CONNECTED ;
64- new Promise ( ( resolve , reject ) => this . activeConnection = { resolve, reject } ) ;
6560
6661 return message ;
6762 }
@@ -90,85 +85,50 @@ export default class NativeAppService {
9085 }
9186 }
9287
93- async disconnectListener ( ) : Promise < void > {
94- config . DEBUG && console . log ( "Native app disconnected" ) ;
88+ disconnectListener ( ) : void {
89+ config . DEBUG && console . log ( "Native app disconnected." ) ;
90+
9591 // Accessing lastError when it exists stops chrome from throwing it unnecessarily.
9692 chrome ?. runtime ?. lastError ;
9793
98- // Defer active connection cleanup for Edge
99- await new Promise ( ( resolve ) => setTimeout ( resolve ) ) ;
100-
101- this . activeConnection ?. resolve ( ) ;
10294 this . state = NativeAppState . DISCONNECTED ;
103-
104- this . pending ?. reject ?.( new UnknownError ( "native application closed the connection before a response" ) ) ;
105- this . pending = null ;
106- }
107-
108- disconnectForcefully ( ) : void {
109- this . state = NativeAppState . DISCONNECTED ;
110-
111- // At this point, requests should already be resolved.
112- // Rejecting a resolved promise is a NOOP.
113- this . pending ?. reject ?.( new UnknownError ( "extension closed connection to native app prematurely" ) ) ;
114- this . pending = null ;
115-
116- this . port ?. disconnect ( ) ;
11795 }
11896
11997 close ( ) : void {
12098 if ( this . state == NativeAppState . DISCONNECTED ) return ;
12199
122- this . disconnectForcefully ( ) ;
100+ this . state = NativeAppState . DISCONNECTED ;
101+ this . port ?. disconnect ( ) ;
102+
103+ config . DEBUG && console . log ( "Native app port closed by extension." ) ;
123104 }
124105
125- send < T > ( message : NativeRequest ) : Promise < T > {
106+ async send < T > ( message : NativeRequest , timeout : number , throwAfterTimeout : Error ) : Promise < T > {
126107 switch ( this . state ) {
127108 case NativeAppState . CONNECTED : {
128- return new Promise ( ( resolve , reject ) => {
129- this . pending = { resolve, reject } ;
130-
131- const onResponse = async ( message : T ) : Promise < void > => {
132- this . port ?. onMessage . removeListener ( onResponse ) ;
109+ config . DEBUG && console . log ( "Sending message to native app" , JSON . stringify ( message ) ) ;
133110
134- try {
135- await Promise . race ( [
136- this . activeConnection ,
137- throwAfterTimeout (
138- config . NATIVE_GRACEFUL_DISCONNECT_TIMEOUT ,
139- new Error ( "Native application did not disconnect after response" )
140- ) ,
141- ] ) ;
111+ const messageSize = calculateJsonSize ( message ) ;
142112
143- } catch ( error ) {
144- console . error ( error ) ;
145- this . disconnectForcefully ( ) ;
146-
147- } finally {
148- const error = ( message as unknown as NativeFailureResponse ) ?. error ;
149-
150- if ( error ) {
151- reject ( deserializeError ( error ) ) ;
152- } else {
153- resolve ( message ) ;
154- }
113+ if ( messageSize > config . NATIVE_MESSAGE_MAX_BYTES ) {
114+ throw new Error ( `native application message exceeded ${ config . NATIVE_MESSAGE_MAX_BYTES } bytes` ) ;
115+ }
155116
156- this . pending = null ;
157- }
158- } ;
117+ this . port ?. postMessage ( message ) ;
159118
160- this . port ?. onMessage . addListener ( onResponse ) ;
119+ try {
120+ const response = await this . nextMessage ( timeout , throwAfterTimeout ) ;
161121
162- config . DEBUG && console . log ( "Sending message to native app" , JSON . stringify ( message ) ) ;
122+ this . close ( ) ;
163123
164- const messageSize = calculateJsonSize ( message ) ;
124+ return response ;
125+ } catch ( error ) {
126+ console . error ( error ) ;
165127
166- if ( messageSize > config . NATIVE_MESSAGE_MAX_BYTES ) {
167- throw new Error ( `native application message exceeded ${ config . NATIVE_MESSAGE_MAX_BYTES } bytes` ) ;
168- }
128+ this . close ( ) ;
169129
170- this . port ?. postMessage ( message ) ;
171- } ) ;
130+ throw error ;
131+ }
172132 }
173133
174134 case NativeAppState . UNINITIALIZED : {
@@ -197,7 +157,7 @@ export default class NativeAppService {
197157 }
198158 }
199159
200- nextMessage ( timeout : number ) : Promise < any > {
160+ nextMessage ( timeout : number , throwAfterTimeout : Error ) : Promise < any > {
201161 return new Promise ( ( resolve , reject ) => {
202162 let cleanup : ( ( ) => void ) | null = null ;
203163 let timer : ReturnType < typeof setTimeout > | null = null ;
@@ -227,9 +187,7 @@ export default class NativeAppService {
227187 timer = setTimeout (
228188 ( ) => {
229189 cleanup ?.( ) ;
230- reject ( new NativeUnavailableError (
231- `a message from native application was expected, but message wasn't received in ${ timeout } ms`
232- ) ) ;
190+ reject ( throwAfterTimeout ) ;
233191 } ,
234192 timeout ,
235193 ) ;
0 commit comments