@@ -72,30 +72,40 @@ export class RustVerificationRequest
72
72
private readonly supportedVerificationMethods : string [ ] ,
73
73
) {
74
74
super ( ) ;
75
-
76
75
this . reEmitter = new TypedReEmitter ( this ) ;
77
76
78
- const onChange = async ( ) : Promise < void > => {
79
- const verification : RustSdkCryptoJs . Qr | RustSdkCryptoJs . Sas | undefined = this . inner . getVerification ( ) ;
80
-
81
- // Set the _verifier object (wrapping the rust `Verification` as a js-sdk Verifier) if:
82
- // - we now have a `Verification` where we lacked one before
83
- // - we have transitioned from QR to SAS
84
- // - we are verifying with SAS, but we need to replace our verifier with a new one because both parties
85
- // tried to start verification at the same time, and we lost the tie breaking
86
- if ( verification instanceof RustSdkCryptoJs . Sas ) {
87
- if ( this . _verifier === undefined || this . _verifier instanceof RustQrCodeVerifier ) {
88
- this . setVerifier ( new RustSASVerifier ( verification , this , outgoingRequestProcessor ) ) ;
89
- } else if ( this . _verifier instanceof RustSASVerifier ) {
90
- this . _verifier . replaceInner ( verification ) ;
91
- }
92
- } else if ( verification instanceof RustSdkCryptoJs . Qr && this . _verifier === undefined ) {
93
- this . setVerifier ( new RustQrCodeVerifier ( verification , outgoingRequestProcessor ) ) ;
77
+ // Obviously, the Rust object maintains a reference to the callback function. If the callback function maintains
78
+ // a reference to the Rust object, then we have a reference cycle which means that `RustVerificationRequest`
79
+ // will never be garbage-collected, and hence the underlying rust object will never be freed.
80
+ //
81
+ // To avoid this reference cycle, use a weak reference in the callback function. If the `RustVerificationRequest`
82
+ // gets garbage-collected, then there is nothing to update!
83
+ const weakThis = new WeakRef ( this ) ;
84
+ inner . registerChangesCallback ( async ( ) => weakThis . deref ( ) ?. onChange ( ) ) ;
85
+ }
86
+
87
+ /**
88
+ * Hook which is called when the underlying rust class notifies us that there has been a change.
89
+ */
90
+ private onChange ( ) : void {
91
+ const verification : RustSdkCryptoJs . Qr | RustSdkCryptoJs . Sas | undefined = this . inner . getVerification ( ) ;
92
+
93
+ // Set the _verifier object (wrapping the rust `Verification` as a js-sdk Verifier) if:
94
+ // - we now have a `Verification` where we lacked one before
95
+ // - we have transitioned from QR to SAS
96
+ // - we are verifying with SAS, but we need to replace our verifier with a new one because both parties
97
+ // tried to start verification at the same time, and we lost the tie breaking
98
+ if ( verification instanceof RustSdkCryptoJs . Sas ) {
99
+ if ( this . _verifier === undefined || this . _verifier instanceof RustQrCodeVerifier ) {
100
+ this . setVerifier ( new RustSASVerifier ( verification , this , this . outgoingRequestProcessor ) ) ;
101
+ } else if ( this . _verifier instanceof RustSASVerifier ) {
102
+ this . _verifier . replaceInner ( verification ) ;
94
103
}
104
+ } else if ( verification instanceof RustSdkCryptoJs . Qr && this . _verifier === undefined ) {
105
+ this . setVerifier ( new RustQrCodeVerifier ( verification , this . outgoingRequestProcessor ) ) ;
106
+ }
95
107
96
- this . emit ( VerificationRequestEvent . Change ) ;
97
- } ;
98
- inner . registerChangesCallback ( onChange ) ;
108
+ this . emit ( VerificationRequestEvent . Change ) ;
99
109
}
100
110
101
111
private setVerifier ( verifier : RustSASVerifier | RustQrCodeVerifier ) : void {
@@ -473,9 +483,12 @@ abstract class BaseRustVerifer<InnerType extends RustSdkCryptoJs.Qr | RustSdkCry
473
483
super ( ) ;
474
484
475
485
this . completionDeferred = defer ( ) ;
476
- inner . registerChangesCallback ( async ( ) => {
477
- this . onChange ( ) ;
478
- } ) ;
486
+
487
+ // As with RustVerificationRequest, we need to avoid a reference cycle.
488
+ // See the comments in RustVerificationRequest.
489
+ const weakThis = new WeakRef ( this ) ;
490
+ inner . registerChangesCallback ( async ( ) => weakThis . deref ( ) ?. onChange ( ) ) ;
491
+
479
492
// stop the runtime complaining if nobody catches a failure
480
493
this . completionDeferred . promise . catch ( ( ) => null ) ;
481
494
}
@@ -752,9 +765,12 @@ export class RustSASVerifier extends BaseRustVerifer<RustSdkCryptoJs.Sas> implem
752
765
public replaceInner ( inner : RustSdkCryptoJs . Sas ) : void {
753
766
if ( this . inner != inner ) {
754
767
this . inner = inner ;
755
- inner . registerChangesCallback ( async ( ) => {
756
- this . onChange ( ) ;
757
- } ) ;
768
+
769
+ // As with RustVerificationRequest, we need to avoid a reference cycle.
770
+ // See the comments in RustVerificationRequest.
771
+ const weakThis = new WeakRef ( this ) ;
772
+ inner . registerChangesCallback ( async ( ) => weakThis . deref ( ) ?. onChange ( ) ) ;
773
+
758
774
// replaceInner will only get called if we started the verification at the same time as the other side, and we lost
759
775
// the tie breaker. So we need to re-accept their verification.
760
776
this . sendAccept ( ) ;
0 commit comments