@firebase/data-connect 0.6.1-20260505164105 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs.js +478 -187
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +478 -187
- package/dist/index.esm.js.map +1 -1
- package/dist/index.node.cjs.js +478 -187
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/node-esm/index.node.esm.js +478 -187
- package/dist/node-esm/index.node.esm.js.map +1 -1
- package/dist/node-esm/src/network/stream/streamTransport.d.ts +163 -54
- package/dist/node-esm/src/network/stream/websocket.d.ts +0 -11
- package/dist/src/network/stream/streamTransport.d.ts +163 -54
- package/dist/src/network/stream/websocket.d.ts +0 -11
- package/package.json +7 -7
|
@@ -520,49 +520,128 @@ class RESTTransport extends AbstractDataConnectTransport {
|
|
|
520
520
|
* See the License for the specific language governing permissions and
|
|
521
521
|
* limitations under the License.
|
|
522
522
|
*/
|
|
523
|
-
/** The
|
|
523
|
+
/** The Request ID of the first request over the stream */
|
|
524
524
|
const FIRST_REQUEST_ID = 1;
|
|
525
|
-
/** Time to wait before closing an idle connection (no active subscriptions) */
|
|
525
|
+
/** Time to wait before closing an idle connection (no active subscriptions). */
|
|
526
526
|
const IDLE_CONNECTION_TIMEOUT_MS = 60 * 1000; // 1 minute
|
|
527
|
+
/** Initial reconnect delay in ms */
|
|
528
|
+
const INITIAL_RECONNECT_DELAY_MS = 1000;
|
|
529
|
+
/** Max reconnect delay in ms */
|
|
530
|
+
const MAX_RECONNECT_DELAY_MS = 30000;
|
|
531
|
+
/** Max random jitter to add to reconnect delay in ms */
|
|
532
|
+
const MAX_RECONNECT_JITTER_MS = 500;
|
|
533
|
+
/** Factor to multiply delay by on failure */
|
|
534
|
+
const RECONNECT_BACKOFF_FACTOR = 1.3;
|
|
535
|
+
/** Max number of reconnection attempts before giving up */
|
|
536
|
+
const MAX_RECONNECT_ATTEMPTS = 10;
|
|
527
537
|
/**
|
|
528
|
-
* The base class for all
|
|
529
|
-
* Handles management of logical streams (requests), authentication, data routing to query layer,
|
|
538
|
+
* The base class for all Stream Transport implementations.
|
|
539
|
+
* Handles management of logical streams (requests), authentication, data routing to query layer,
|
|
540
|
+
* request optimizations, etc.
|
|
530
541
|
* @internal
|
|
531
542
|
*/
|
|
532
543
|
class AbstractDataConnectStreamTransport extends AbstractDataConnectTransport {
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
this.
|
|
544
|
+
/** Is the stream currently waiting to close connection? */
|
|
545
|
+
get isPendingClose() {
|
|
546
|
+
return !!this.idleTimeout;
|
|
547
|
+
}
|
|
548
|
+
/** True if there are active subscriptions on the stream */
|
|
549
|
+
get hasActiveSubscriptions() {
|
|
550
|
+
return this.activeInvokeSubscribeRequests.size > 0;
|
|
551
|
+
}
|
|
552
|
+
/** True if there are active execute or mutation requests on the stream */
|
|
553
|
+
get hasActiveExecuteRequests() {
|
|
554
|
+
return (this.activeInvokeQueryRequests.size > 0 ||
|
|
555
|
+
this.activeInvokeMutationRequests.size > 0);
|
|
556
|
+
}
|
|
557
|
+
constructor(options, apiKey, appId, authProvider, appCheckProvider, transportOptions, _isUsingGen = false, _callerSdkType = CallerSdkTypeEnum.Base) {
|
|
558
|
+
super(options, apiKey, appId, authProvider, appCheckProvider, transportOptions, _isUsingGen, _callerSdkType);
|
|
559
|
+
this.apiKey = apiKey;
|
|
560
|
+
this.appId = appId;
|
|
561
|
+
this.authProvider = authProvider;
|
|
562
|
+
this.appCheckProvider = appCheckProvider;
|
|
563
|
+
this._isUsingGen = _isUsingGen;
|
|
564
|
+
this._callerSdkType = _callerSdkType;
|
|
536
565
|
/** True if the transport is unable to connect to the server */
|
|
537
566
|
this.isUnableToConnect = false;
|
|
538
|
-
/** The
|
|
567
|
+
/** The Request ID of the next message to be sent. Monotonically increasing sequence number starting at {@linkcode FIRST_REQUEST_ID}. */
|
|
539
568
|
this.requestNumber = FIRST_REQUEST_ID;
|
|
540
569
|
/**
|
|
541
|
-
* Map of query/variables to their active
|
|
570
|
+
* Map of query/variables to their active {@linkcode ExecuteStreamRequest} or {@linkcode ResumeStreamRequest}
|
|
571
|
+
* request bodies. These requests are de-duplicated by query/variables so that there is only one active
|
|
572
|
+
* request for each query/variables combination.
|
|
542
573
|
*/
|
|
543
|
-
this.
|
|
574
|
+
this.activeInvokeQueryRequests = new Map();
|
|
544
575
|
/**
|
|
545
|
-
* Map of
|
|
576
|
+
* Map of query/variables to the promises returned to the user, for invokeQuery requests which are
|
|
577
|
+
* queued and waiting for active request to resolve.
|
|
546
578
|
*/
|
|
547
|
-
this.
|
|
579
|
+
this.queuedInvokeQueryRequests = new Map();
|
|
548
580
|
/**
|
|
549
|
-
* Map of
|
|
581
|
+
* Map of mutation/variables to their active {@linkcode ExecuteStreamRequest} request bodies. Mutations
|
|
582
|
+
* can have more than one active request at a time as they are not idempotent, and therefore should
|
|
583
|
+
* not be de-duplicated.
|
|
550
584
|
*/
|
|
551
|
-
this.
|
|
585
|
+
this.activeInvokeMutationRequests = new Map();
|
|
552
586
|
/**
|
|
553
|
-
* Map of
|
|
587
|
+
* Map of query/variables to their active {@linkcode SubscribeStreamRequest} request bodies. There
|
|
588
|
+
* may only be one active request for each query/variables combination.
|
|
589
|
+
*/
|
|
590
|
+
this.activeInvokeSubscribeRequests = new Map();
|
|
591
|
+
/**
|
|
592
|
+
* Map of active {@linkcode ExecuteStreamRequest} RequestIds from {@linkcode invokeQuery} and {@linkcode invokeMutation},
|
|
593
|
+
* and their corresponding {@linkcode InvokeOperationPromise}.
|
|
554
594
|
*/
|
|
555
595
|
this.executeRequestPromises = new Map();
|
|
556
596
|
/**
|
|
557
|
-
* Map of active
|
|
597
|
+
* Map of active {@linkcode ResumeStreamRequest} RequestIds from {@linkcode invokeQuery}, and their
|
|
598
|
+
* corresponding {@linkcode InvokeOperationPromise}.
|
|
599
|
+
*/
|
|
600
|
+
this.resumeRequestPromises = new Map();
|
|
601
|
+
/**
|
|
602
|
+
* Map of active {@linkcode invokeSubscribe} RequestIds and their corresponding {@linkcode SubscribeObserver}.
|
|
558
603
|
*/
|
|
559
604
|
this.subscribeObservers = new Map();
|
|
560
|
-
/**
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
605
|
+
/**
|
|
606
|
+
* Map of subscribe RequestIds to deferred unsubscription requests. Used when a client unsubscribes
|
|
607
|
+
* while a resume request is actively pending.
|
|
608
|
+
*/
|
|
609
|
+
this.pendingCancellations = new Map();
|
|
610
|
+
/** current idle timeout, if any */
|
|
611
|
+
this.idleTimeout = null;
|
|
564
612
|
/** Flag to ensure we wait for the initial auth state once per connection attempt. */
|
|
565
613
|
this.hasWaitedForInitialAuth = false;
|
|
614
|
+
/** Delay for next reconnection attempt in ms */
|
|
615
|
+
this.reconnectDelayMs = INITIAL_RECONNECT_DELAY_MS;
|
|
616
|
+
/** Timer for reconnection */
|
|
617
|
+
this.reconnectTimer = null;
|
|
618
|
+
/** Number of consecutive reconnection attempts */
|
|
619
|
+
this.reconnectAttempts = 0;
|
|
620
|
+
/** Callback to remove online event listener */
|
|
621
|
+
this.removeOnlineEventListener = null;
|
|
622
|
+
/** Callback to remove visibility change event listener */
|
|
623
|
+
this.removeVisibilityChangeEventListener = null;
|
|
624
|
+
/**
|
|
625
|
+
* Short-circuit a reconnection attempt, if one is pending. Triggered when an online event is
|
|
626
|
+
* dispatched.
|
|
627
|
+
*/
|
|
628
|
+
this.onOnlineEventListener = () => {
|
|
629
|
+
if (this.reconnectTimer) {
|
|
630
|
+
this.cancelReconnect();
|
|
631
|
+
void this.attemptReconnect();
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
/**
|
|
635
|
+
* Short-circuit a reconnection attempt, if one is pending. Triggered when a visibility change
|
|
636
|
+
* event is dispatched.
|
|
637
|
+
*/
|
|
638
|
+
this.onVisibilityChangeEventListener = () => {
|
|
639
|
+
const doc = globalThis.document;
|
|
640
|
+
if (doc && doc.visibilityState === 'visible' && this.reconnectTimer) {
|
|
641
|
+
this.cancelReconnect();
|
|
642
|
+
void this.attemptReconnect();
|
|
643
|
+
}
|
|
644
|
+
};
|
|
566
645
|
/**
|
|
567
646
|
* Tracks if the next message to be sent is the first message of the stream.
|
|
568
647
|
*/
|
|
@@ -572,62 +651,62 @@ class AbstractDataConnectStreamTransport extends AbstractDataConnectTransport {
|
|
|
572
651
|
* Used to detect if the token has changed and needs to be resent.
|
|
573
652
|
*/
|
|
574
653
|
this.lastSentAuthToken = null;
|
|
654
|
+
this.registerBrowserEventListeners();
|
|
575
655
|
}
|
|
576
|
-
/**
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
656
|
+
/**
|
|
657
|
+
* Register event listeners for browser-specific events like online/offline and visibility changes.
|
|
658
|
+
*/
|
|
659
|
+
registerBrowserEventListeners() {
|
|
660
|
+
if ('addEventListener' in globalThis) {
|
|
661
|
+
const listener = this.onOnlineEventListener;
|
|
662
|
+
globalThis.addEventListener('online', listener);
|
|
663
|
+
this.removeOnlineEventListener = () => globalThis.removeEventListener('online', listener);
|
|
664
|
+
}
|
|
665
|
+
const doc = globalThis.document;
|
|
666
|
+
if (doc && 'addEventListener' in doc) {
|
|
667
|
+
const listener = this.onVisibilityChangeEventListener;
|
|
668
|
+
doc.addEventListener('visibilitychange', listener);
|
|
669
|
+
this.removeVisibilityChangeEventListener = () => doc.removeEventListener('visibilitychange', listener);
|
|
670
|
+
}
|
|
583
671
|
}
|
|
584
|
-
/**
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
672
|
+
/**
|
|
673
|
+
* Remove event listeners registered by {@linkcode AbstractDataConnectStreamTransport.registerBrowserEventListeners | registerBrowserEventListeners()}
|
|
674
|
+
* for browser-specific events like online/offline and visibility changes.
|
|
675
|
+
*/
|
|
676
|
+
cleanupBrowserEventListeners() {
|
|
677
|
+
this.removeVisibilityChangeEventListener?.();
|
|
678
|
+
this.removeVisibilityChangeEventListener = null;
|
|
679
|
+
this.removeOnlineEventListener?.();
|
|
680
|
+
this.removeOnlineEventListener = null;
|
|
588
681
|
}
|
|
589
682
|
/**
|
|
590
|
-
*
|
|
683
|
+
* Disposes of the transport instance, cleaning up event listeners and timers,
|
|
684
|
+
* and closing the connection.
|
|
591
685
|
*/
|
|
592
|
-
|
|
593
|
-
|
|
686
|
+
async cleanupAndTerminate(code, reason) {
|
|
687
|
+
this.cleanupBrowserEventListeners();
|
|
688
|
+
this.cancelReconnect();
|
|
689
|
+
this.cancelClose();
|
|
690
|
+
this.rejectAllRequests(code ?? Code.OTHER, reason ?? 'Stream disposed.');
|
|
691
|
+
await this.closeConnection();
|
|
692
|
+
this.onCloseCallback?.();
|
|
594
693
|
}
|
|
595
694
|
/**
|
|
596
|
-
*
|
|
597
|
-
*
|
|
598
|
-
* @returns The reject function and the response promise.
|
|
599
|
-
*
|
|
600
|
-
* @remarks
|
|
601
|
-
* This method returns a promise, but is synchronous.
|
|
695
|
+
* Generates and returns the next Request ID. Starts at {@linkcode FIRST_REQUEST_ID} and increments
|
|
696
|
+
* for each request sent.
|
|
602
697
|
*/
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
let resolveFn;
|
|
606
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
607
|
-
let rejectFn;
|
|
608
|
-
const responsePromise = new Promise((resolve, reject) => {
|
|
609
|
-
resolveFn = resolve;
|
|
610
|
-
rejectFn = reject;
|
|
611
|
-
});
|
|
612
|
-
const executeRequestPromise = {
|
|
613
|
-
responsePromise,
|
|
614
|
-
resolveFn: resolveFn,
|
|
615
|
-
rejectFn: rejectFn
|
|
616
|
-
};
|
|
617
|
-
this.activeQueryExecuteRequests.set(mapKey, executeBody);
|
|
618
|
-
this.executeRequestPromises.set(requestId, executeRequestPromise);
|
|
619
|
-
return executeRequestPromise;
|
|
698
|
+
nextRequestId() {
|
|
699
|
+
return (this.requestNumber++).toString();
|
|
620
700
|
}
|
|
621
701
|
/**
|
|
622
|
-
* Tracks
|
|
623
|
-
* that will be resolved when the response is received.
|
|
624
|
-
* @returns The
|
|
702
|
+
* Tracks an {@linkcode invokeMutation} request, storing the request body and creating and storing a
|
|
703
|
+
* response promise that will be resolved when the response is received.
|
|
704
|
+
* @returns The tracked {@linkcode InvokeOperationPromise}.
|
|
625
705
|
*
|
|
626
706
|
* @remarks
|
|
627
707
|
* This method returns a promise, but is synchronous.
|
|
628
708
|
*/
|
|
629
|
-
|
|
630
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
709
|
+
trackInvokeMutationRequest(requestId, mapKey, executeBody) {
|
|
631
710
|
let resolveFn;
|
|
632
711
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
633
712
|
let rejectFn;
|
|
@@ -640,42 +719,43 @@ class AbstractDataConnectStreamTransport extends AbstractDataConnectTransport {
|
|
|
640
719
|
resolveFn: resolveFn,
|
|
641
720
|
rejectFn: rejectFn
|
|
642
721
|
};
|
|
643
|
-
const activeRequests = this.
|
|
722
|
+
const activeRequests = this.activeInvokeMutationRequests.get(mapKey) || [];
|
|
644
723
|
activeRequests.push(executeBody);
|
|
645
|
-
this.
|
|
724
|
+
this.activeInvokeMutationRequests.set(mapKey, activeRequests);
|
|
646
725
|
this.executeRequestPromises.set(requestId, executeRequestPromise);
|
|
647
726
|
return executeRequestPromise;
|
|
648
727
|
}
|
|
649
728
|
/**
|
|
650
|
-
* Tracks
|
|
729
|
+
* Tracks an {@linkcode invokeSubscribe} request, storing the request body and the {@linkcode SubscribeObserver}.
|
|
651
730
|
* @remarks
|
|
652
731
|
* This method is synchronous.
|
|
653
732
|
*/
|
|
654
|
-
|
|
655
|
-
this.
|
|
733
|
+
trackInvokeSubscribeRequest(requestId, mapKey, subscribeBody, observer) {
|
|
734
|
+
this.activeInvokeSubscribeRequests.set(mapKey, subscribeBody);
|
|
656
735
|
this.subscribeObservers.set(requestId, observer);
|
|
657
736
|
}
|
|
658
737
|
/**
|
|
659
738
|
* Cleans up the query execute request tracking data structures, deleting the tracked request and
|
|
660
739
|
* it's associated promise.
|
|
661
740
|
*/
|
|
662
|
-
|
|
663
|
-
this.
|
|
741
|
+
cleanupInvokeQueryRequest(requestId, mapKey) {
|
|
742
|
+
this.activeInvokeQueryRequests.delete(mapKey);
|
|
664
743
|
this.executeRequestPromises.delete(requestId);
|
|
744
|
+
this.resumeRequestPromises.delete(requestId);
|
|
665
745
|
}
|
|
666
746
|
/**
|
|
667
747
|
* Cleans up the mutation execute request tracking data structures, deleting the tracked request and
|
|
668
748
|
* it's associated promise.
|
|
669
749
|
*/
|
|
670
|
-
|
|
671
|
-
const executeRequests = this.
|
|
750
|
+
cleanupInvokeMutationRequest(requestId, mapKey) {
|
|
751
|
+
const executeRequests = this.activeInvokeMutationRequests.get(mapKey);
|
|
672
752
|
if (executeRequests) {
|
|
673
|
-
const updatedRequests = executeRequests.filter(
|
|
753
|
+
const updatedRequests = executeRequests.filter(request => request.requestId !== requestId);
|
|
674
754
|
if (updatedRequests.length > 0) {
|
|
675
|
-
this.
|
|
755
|
+
this.activeInvokeMutationRequests.set(mapKey, updatedRequests);
|
|
676
756
|
}
|
|
677
757
|
else {
|
|
678
|
-
this.
|
|
758
|
+
this.activeInvokeMutationRequests.delete(mapKey);
|
|
679
759
|
}
|
|
680
760
|
}
|
|
681
761
|
this.executeRequestPromises.delete(requestId);
|
|
@@ -684,10 +764,72 @@ class AbstractDataConnectStreamTransport extends AbstractDataConnectTransport {
|
|
|
684
764
|
* Cleans up the subscribe request tracking data structures, deleting the tracked request and
|
|
685
765
|
* it's associated promise.
|
|
686
766
|
*/
|
|
687
|
-
|
|
688
|
-
this.
|
|
767
|
+
cleanupInvokeSubscribeRequest(requestId, mapKey) {
|
|
768
|
+
this.activeInvokeSubscribeRequests.delete(mapKey);
|
|
689
769
|
this.subscribeObservers.delete(requestId);
|
|
690
770
|
}
|
|
771
|
+
/**
|
|
772
|
+
* Cancel reconnecting.
|
|
773
|
+
*/
|
|
774
|
+
cancelReconnect() {
|
|
775
|
+
if (this.reconnectTimer) {
|
|
776
|
+
clearTimeout(this.reconnectTimer);
|
|
777
|
+
this.reconnectTimer = null;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Starts the backoff timer for reconnection attempts. We use an exponential backoff with randomized
|
|
782
|
+
* jitter to prevent overwhelming the backend with connection attempts.
|
|
783
|
+
*/
|
|
784
|
+
startReconnectBackoff() {
|
|
785
|
+
if (this.reconnectTimer) {
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
if (this.reconnectAttempts++ >= MAX_RECONNECT_ATTEMPTS) {
|
|
789
|
+
const errorString = 'Stream disconnected and could not reconnect - max stream reconnection attempts reached.';
|
|
790
|
+
logError(errorString);
|
|
791
|
+
void this.cleanupAndTerminate(Code.OTHER, errorString);
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
const delay = this.reconnectDelayMs;
|
|
795
|
+
this.reconnectDelayMs = Math.min(this.reconnectDelayMs * RECONNECT_BACKOFF_FACTOR, MAX_RECONNECT_DELAY_MS);
|
|
796
|
+
const jitter = Math.random() * MAX_RECONNECT_JITTER_MS;
|
|
797
|
+
this.reconnectTimer = setTimeout(() => {
|
|
798
|
+
this.reconnectTimer = null;
|
|
799
|
+
void this.attemptReconnect();
|
|
800
|
+
}, delay + jitter);
|
|
801
|
+
}
|
|
802
|
+
async attemptReconnect() {
|
|
803
|
+
try {
|
|
804
|
+
await this.ensureConnection();
|
|
805
|
+
// reset on success
|
|
806
|
+
this.reconnectDelayMs = INITIAL_RECONNECT_DELAY_MS;
|
|
807
|
+
this.reconnectAttempts = 0;
|
|
808
|
+
await this.retriggerActiveRequests();
|
|
809
|
+
}
|
|
810
|
+
catch (e) {
|
|
811
|
+
if (e instanceof FirebaseError) {
|
|
812
|
+
logDebug(`Reconnect attempt #${this.reconnectAttempts} failed with Firebase error: ${e.message}. Retrying...`);
|
|
813
|
+
this.startReconnectBackoff();
|
|
814
|
+
}
|
|
815
|
+
else {
|
|
816
|
+
logError(`Unexpected error during reconnect attempt #${this.reconnectAttempts}: ${e}`);
|
|
817
|
+
void this.cleanupAndTerminate(Code.OTHER, `Unexpected error during reconnect attempt #${this.reconnectAttempts}: ${e}`);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Retriggers all active requests on the stream connection - first subscribes, then query executions,
|
|
823
|
+
* and skip mutations. Used after a successful reconnection.
|
|
824
|
+
*/
|
|
825
|
+
async retriggerActiveRequests() {
|
|
826
|
+
for (const [_, subscribeBody] of this.activeInvokeSubscribeRequests) {
|
|
827
|
+
await this.sendRequestMessage(subscribeBody);
|
|
828
|
+
}
|
|
829
|
+
for (const [_, requestBody] of this.activeInvokeQueryRequests) {
|
|
830
|
+
await this.sendRequestMessage(requestBody);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
691
833
|
/**
|
|
692
834
|
* Indicates whether we should include the auth token in the next message.
|
|
693
835
|
* Only true if there is an auth token and it is different from the last sent auth token, or this
|
|
@@ -706,66 +848,91 @@ class AbstractDataConnectStreamTransport extends AbstractDataConnectTransport {
|
|
|
706
848
|
this.hasWaitedForInitialAuth = false;
|
|
707
849
|
}
|
|
708
850
|
/**
|
|
709
|
-
*
|
|
710
|
-
*
|
|
851
|
+
* Begin closing the connection. Waits for {@linkcode IDLE_CONNECTION_TIMEOUT_MS} without cleaning up
|
|
852
|
+
* any requests (meaning it will not close after the timeout unless the requests are closed first).
|
|
853
|
+
* This is a graceful close - it will be called when there are no more active subscriptions, so
|
|
854
|
+
* there's no need to cleanup.
|
|
711
855
|
*/
|
|
712
|
-
|
|
713
|
-
if (this.
|
|
856
|
+
startIdleCloseTimeout() {
|
|
857
|
+
if (this.idleTimeout) {
|
|
714
858
|
return;
|
|
715
859
|
}
|
|
716
|
-
this.
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
* no more active subscriptions, so there's no need to cleanup.
|
|
724
|
-
*/
|
|
725
|
-
prepareToCloseGracefully() {
|
|
726
|
-
if (this.pendingClose) {
|
|
727
|
-
return;
|
|
728
|
-
}
|
|
729
|
-
this.pendingClose = true;
|
|
730
|
-
this.closeTimeoutFinished = false;
|
|
731
|
-
this.closeTimeout = setTimeout(() => {
|
|
732
|
-
this.closeTimeoutFinished = true;
|
|
733
|
-
void this.attemptClose();
|
|
860
|
+
this.idleTimeout = setTimeout(() => {
|
|
861
|
+
this.idleTimeout = null;
|
|
862
|
+
// Safety check: Don't close if new requests arrived during the timeout!
|
|
863
|
+
if (this.hasActiveSubscriptions || this.hasActiveExecuteRequests) {
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
void this.cleanupAndTerminate(Code.OTHER, 'Stream closed due to idleness.');
|
|
734
867
|
}, IDLE_CONNECTION_TIMEOUT_MS);
|
|
735
868
|
}
|
|
736
869
|
/**
|
|
737
870
|
* Cancel closing the connection.
|
|
738
871
|
*/
|
|
739
872
|
cancelClose() {
|
|
740
|
-
if (this.
|
|
741
|
-
clearTimeout(this.
|
|
873
|
+
if (this.idleTimeout) {
|
|
874
|
+
clearTimeout(this.idleTimeout);
|
|
875
|
+
this.idleTimeout = null;
|
|
742
876
|
}
|
|
743
|
-
this.pendingClose = false;
|
|
744
|
-
this.closeTimeoutFinished = false;
|
|
745
877
|
}
|
|
746
878
|
/**
|
|
747
879
|
* Reject all active execute promises and notify all subscribe observers with the given error.
|
|
748
880
|
* Clear active request tracking maps without cancelling or re-invoking any requests.
|
|
749
881
|
*/
|
|
750
|
-
|
|
751
|
-
this.
|
|
752
|
-
this.
|
|
753
|
-
this.
|
|
882
|
+
rejectAllRequests(code, reason) {
|
|
883
|
+
this.activeInvokeQueryRequests.clear();
|
|
884
|
+
this.activeInvokeMutationRequests.clear();
|
|
885
|
+
this.activeInvokeSubscribeRequests.clear();
|
|
754
886
|
const error = new DataConnectError(code, reason);
|
|
887
|
+
for (const [mapKey, { rejectFn }] of this.queuedInvokeQueryRequests) {
|
|
888
|
+
this.queuedInvokeQueryRequests.delete(mapKey);
|
|
889
|
+
rejectFn(error);
|
|
890
|
+
}
|
|
755
891
|
for (const [requestId, { rejectFn }] of this.executeRequestPromises) {
|
|
756
892
|
this.executeRequestPromises.delete(requestId);
|
|
757
893
|
rejectFn(error);
|
|
758
894
|
}
|
|
895
|
+
for (const [requestId, { rejectFn }] of this.resumeRequestPromises) {
|
|
896
|
+
this.resumeRequestPromises.delete(requestId);
|
|
897
|
+
rejectFn(error);
|
|
898
|
+
}
|
|
759
899
|
for (const [requestId, observer] of this.subscribeObservers) {
|
|
760
900
|
this.subscribeObservers.delete(requestId);
|
|
761
901
|
observer.onDisconnect(code, reason);
|
|
762
902
|
}
|
|
903
|
+
this.pendingCancellations.clear();
|
|
904
|
+
this.cancelReconnect();
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Reject all mutation execute promises.
|
|
908
|
+
* Clear active request tracking maps without cancelling or re-invoking any requests.
|
|
909
|
+
*/
|
|
910
|
+
rejectAllMutationsOnReconnect() {
|
|
911
|
+
const error = new DataConnectError(Code.OTHER, 'Mutation aborted due to stream disconnect.');
|
|
912
|
+
for (const [_, requests] of this.activeInvokeMutationRequests) {
|
|
913
|
+
for (const request of requests) {
|
|
914
|
+
const promise = this.executeRequestPromises.get(request.requestId);
|
|
915
|
+
if (promise) {
|
|
916
|
+
promise.rejectFn(error);
|
|
917
|
+
this.executeRequestPromises.delete(request.requestId);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
this.activeInvokeMutationRequests.clear();
|
|
763
922
|
}
|
|
764
923
|
/**
|
|
765
924
|
* Called by concrete implementations when the stream is successfully closed, gracefully or otherwise.
|
|
766
925
|
*/
|
|
767
926
|
onStreamClose(code, reason) {
|
|
768
|
-
this.
|
|
927
|
+
this.cancelClose();
|
|
928
|
+
if (!this.hasActiveSubscriptions) {
|
|
929
|
+
// skip reconnection if there are no active subscriptions
|
|
930
|
+
void this.cleanupAndTerminate(Code.OTHER, `Stream disconnected while idle with code ${code}: ${reason}`);
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
logDebug(`Stream disconnected with code ${code}: ${reason}. Attempting reconnect...`);
|
|
934
|
+
this.rejectAllMutationsOnReconnect();
|
|
935
|
+
this.startReconnectBackoff();
|
|
769
936
|
}
|
|
770
937
|
/**
|
|
771
938
|
* Prepares a stream request message by adding necessary headers and metadata.
|
|
@@ -797,7 +964,6 @@ class AbstractDataConnectStreamTransport extends AbstractDataConnectTransport {
|
|
|
797
964
|
this.isFirstStreamMessage = false;
|
|
798
965
|
return preparedRequestBody;
|
|
799
966
|
}
|
|
800
|
-
// TODO(stephenarosaj): just make this async
|
|
801
967
|
/**
|
|
802
968
|
* Sends a request message to the server via the concrete implementation.
|
|
803
969
|
* Ensures the connection is ready and prepares the message before sending.
|
|
@@ -818,7 +984,7 @@ class AbstractDataConnectStreamTransport extends AbstractDataConnectTransport {
|
|
|
818
984
|
});
|
|
819
985
|
}
|
|
820
986
|
/**
|
|
821
|
-
* Helper to generate a consistent string key for the tracking maps.
|
|
987
|
+
* Helper to generate a consistent string key for the request tracking maps.
|
|
822
988
|
*/
|
|
823
989
|
getMapKey(operationName, variables) {
|
|
824
990
|
const sortedVariables = this.sortObjectKeys(variables);
|
|
@@ -847,28 +1013,114 @@ class AbstractDataConnectStreamTransport extends AbstractDataConnectTransport {
|
|
|
847
1013
|
* preserves the synchronous update of the tracking data structures before the method returns.
|
|
848
1014
|
*/
|
|
849
1015
|
invokeQuery(queryName, variables) {
|
|
850
|
-
const requestId = this.nextRequestId();
|
|
851
|
-
const activeRequestKey = { operationName: queryName, variables };
|
|
852
1016
|
const mapKey = this.getMapKey(queryName, variables);
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
1017
|
+
if (this.activeInvokeQueryRequests.has(mapKey)) {
|
|
1018
|
+
return this.queueInvokeQueryRequest(mapKey);
|
|
1019
|
+
}
|
|
1020
|
+
return this.executeOrResumeQuery(queryName, variables, mapKey);
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Queue a new query execute request to be executed after the currently active query execute
|
|
1024
|
+
* request resolves, and track + return a promise associated with the queued request. If there is
|
|
1025
|
+
* already a queued request for this mapKey, return the existing queued request's promise instead.
|
|
1026
|
+
*/
|
|
1027
|
+
queueInvokeQueryRequest(mapKey) {
|
|
1028
|
+
const existingQueued = this.queuedInvokeQueryRequests.get(mapKey);
|
|
1029
|
+
if (existingQueued) {
|
|
1030
|
+
// only queue one request per mapKey - return existing queued request promise
|
|
1031
|
+
return existingQueued.responsePromise;
|
|
1032
|
+
}
|
|
1033
|
+
let resolveFn;
|
|
1034
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1035
|
+
let rejectFn;
|
|
1036
|
+
const responsePromise = new Promise((resolve, reject) => {
|
|
1037
|
+
resolveFn = resolve;
|
|
1038
|
+
rejectFn = reject;
|
|
1039
|
+
});
|
|
1040
|
+
this.queuedInvokeQueryRequests.set(mapKey, {
|
|
1041
|
+
responsePromise,
|
|
1042
|
+
resolveFn: resolveFn,
|
|
1043
|
+
rejectFn: rejectFn
|
|
1044
|
+
});
|
|
1045
|
+
return responsePromise;
|
|
1046
|
+
}
|
|
1047
|
+
/**
|
|
1048
|
+
* Executes or resumes a query. Does not check for any active requests which may be overwritten by
|
|
1049
|
+
* this request - this should be handled by the caller.
|
|
1050
|
+
*/
|
|
1051
|
+
executeOrResumeQuery(queryName, variables, mapKey, queuedInvokeOperationPromise) {
|
|
1052
|
+
const activeSubscription = this.activeInvokeSubscribeRequests.get(mapKey);
|
|
1053
|
+
let resolveFn;
|
|
1054
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1055
|
+
let rejectFn;
|
|
1056
|
+
let responsePromise;
|
|
1057
|
+
// track the existing queued promise if one exists - otherwise create a new one
|
|
1058
|
+
if (queuedInvokeOperationPromise) {
|
|
1059
|
+
resolveFn = queuedInvokeOperationPromise.resolveFn;
|
|
1060
|
+
rejectFn = queuedInvokeOperationPromise.rejectFn;
|
|
1061
|
+
responsePromise = queuedInvokeOperationPromise.responsePromise;
|
|
1062
|
+
}
|
|
1063
|
+
else {
|
|
1064
|
+
responsePromise = new Promise((resolve, reject) => {
|
|
1065
|
+
resolveFn = resolve;
|
|
1066
|
+
rejectFn = reject;
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
let requestId;
|
|
1070
|
+
let requestBody;
|
|
1071
|
+
if (activeSubscription) {
|
|
1072
|
+
// resume!
|
|
1073
|
+
requestId = activeSubscription.requestId;
|
|
1074
|
+
requestBody = { requestId, resume: {} };
|
|
1075
|
+
this.resumeRequestPromises.set(requestId, {
|
|
1076
|
+
responsePromise,
|
|
1077
|
+
resolveFn: resolveFn,
|
|
1078
|
+
rejectFn: rejectFn
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
else {
|
|
1082
|
+
// execute!
|
|
1083
|
+
requestId = this.nextRequestId();
|
|
1084
|
+
requestBody = {
|
|
1085
|
+
requestId,
|
|
1086
|
+
execute: { operationName: queryName, variables }
|
|
1087
|
+
};
|
|
1088
|
+
this.executeRequestPromises.set(requestId, {
|
|
1089
|
+
responsePromise,
|
|
1090
|
+
resolveFn: resolveFn,
|
|
1091
|
+
rejectFn: rejectFn
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
this.activeInvokeQueryRequests.set(mapKey, requestBody);
|
|
858
1095
|
responsePromise = responsePromise.finally(() => {
|
|
859
|
-
this.
|
|
860
|
-
if (!this.hasActiveSubscriptions &&
|
|
861
|
-
!this.hasActiveExecuteRequests &&
|
|
862
|
-
this.closeTimeoutFinished) {
|
|
863
|
-
void this.attemptClose();
|
|
864
|
-
}
|
|
1096
|
+
this.onInvokeQueryRequestFulfilled(queryName, variables, mapKey, requestId);
|
|
865
1097
|
});
|
|
866
|
-
|
|
867
|
-
this.sendRequestMessage(executeBody).catch(err => {
|
|
1098
|
+
this.sendRequestMessage(requestBody).catch(err => {
|
|
868
1099
|
rejectFn(err);
|
|
869
1100
|
});
|
|
870
1101
|
return responsePromise;
|
|
871
1102
|
}
|
|
1103
|
+
/**
|
|
1104
|
+
* When a query invoke request is fulfilled, clean up and trigger the next queued
|
|
1105
|
+
* request if one exists.
|
|
1106
|
+
*/
|
|
1107
|
+
onInvokeQueryRequestFulfilled(queryName, variables, mapKey, requestId) {
|
|
1108
|
+
this.cleanupInvokeQueryRequest(requestId, mapKey);
|
|
1109
|
+
const deferredCancel = this.pendingCancellations.get(requestId);
|
|
1110
|
+
if (deferredCancel) {
|
|
1111
|
+
this.pendingCancellations.delete(requestId);
|
|
1112
|
+
this.cancelSubscription(requestId, mapKey);
|
|
1113
|
+
}
|
|
1114
|
+
const queuedRequestPromise = this.queuedInvokeQueryRequests.get(mapKey);
|
|
1115
|
+
if (!queuedRequestPromise) {
|
|
1116
|
+
if (!this.hasActiveSubscriptions && !this.hasActiveExecuteRequests) {
|
|
1117
|
+
this.startIdleCloseTimeout();
|
|
1118
|
+
}
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
this.queuedInvokeQueryRequests.delete(mapKey);
|
|
1122
|
+
void this.executeOrResumeQuery(queryName, variables, mapKey, queuedRequestPromise);
|
|
1123
|
+
}
|
|
872
1124
|
/**
|
|
873
1125
|
* @inheritdoc
|
|
874
1126
|
* @remarks
|
|
@@ -884,13 +1136,11 @@ class AbstractDataConnectStreamTransport extends AbstractDataConnectTransport {
|
|
|
884
1136
|
requestId,
|
|
885
1137
|
execute: activeRequestKey
|
|
886
1138
|
};
|
|
887
|
-
let { responsePromise, rejectFn } = this.
|
|
1139
|
+
let { responsePromise, rejectFn } = this.trackInvokeMutationRequest(requestId, mapKey, executeBody);
|
|
888
1140
|
responsePromise = responsePromise.finally(() => {
|
|
889
|
-
this.
|
|
890
|
-
if (!this.hasActiveSubscriptions &&
|
|
891
|
-
|
|
892
|
-
this.closeTimeoutFinished) {
|
|
893
|
-
void this.attemptClose();
|
|
1141
|
+
this.cleanupInvokeMutationRequest(requestId, mapKey);
|
|
1142
|
+
if (!this.hasActiveSubscriptions && !this.hasActiveExecuteRequests) {
|
|
1143
|
+
this.startIdleCloseTimeout();
|
|
894
1144
|
}
|
|
895
1145
|
});
|
|
896
1146
|
// asynchronous, fire and forget
|
|
@@ -908,24 +1158,35 @@ class AbstractDataConnectStreamTransport extends AbstractDataConnectTransport {
|
|
|
908
1158
|
* before the method returns.
|
|
909
1159
|
*/
|
|
910
1160
|
invokeSubscribe(observer, queryName, variables) {
|
|
911
|
-
// if we are waiting to close the stream, cancel closing!
|
|
912
|
-
this.cancelClose();
|
|
913
|
-
const requestId = this.nextRequestId();
|
|
914
|
-
const activeRequestKey = { operationName: queryName, variables };
|
|
915
1161
|
const mapKey = this.getMapKey(queryName, variables);
|
|
916
|
-
const
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
observer.onError(err instanceof Error ? err : new Error(String(err)));
|
|
924
|
-
this.cleanupSubscribeRequest(requestId, mapKey);
|
|
925
|
-
if (!this.hasActiveSubscriptions) {
|
|
926
|
-
this.prepareToCloseGracefully();
|
|
1162
|
+
const existingSubscribe = this.activeInvokeSubscribeRequests.get(mapKey);
|
|
1163
|
+
// if this query is pending cancellation, cancel the cancellation!
|
|
1164
|
+
if (existingSubscribe) {
|
|
1165
|
+
const requestId = existingSubscribe.requestId;
|
|
1166
|
+
if (this.pendingCancellations.has(requestId)) {
|
|
1167
|
+
this.pendingCancellations.delete(requestId);
|
|
1168
|
+
this.subscribeObservers.set(requestId, observer);
|
|
927
1169
|
}
|
|
928
|
-
}
|
|
1170
|
+
}
|
|
1171
|
+
else {
|
|
1172
|
+
const requestId = this.nextRequestId();
|
|
1173
|
+
const activeRequestKey = { operationName: queryName, variables };
|
|
1174
|
+
const subscribeBody = {
|
|
1175
|
+
requestId,
|
|
1176
|
+
subscribe: activeRequestKey
|
|
1177
|
+
};
|
|
1178
|
+
this.trackInvokeSubscribeRequest(requestId, mapKey, subscribeBody, observer);
|
|
1179
|
+
// asynchronous, fire and forget
|
|
1180
|
+
this.sendRequestMessage(subscribeBody).catch(err => {
|
|
1181
|
+
observer.onError(err instanceof Error ? err : new Error(String(err)));
|
|
1182
|
+
this.cleanupInvokeSubscribeRequest(requestId, mapKey);
|
|
1183
|
+
if (!this.hasActiveSubscriptions) {
|
|
1184
|
+
this.startIdleCloseTimeout();
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
// if we are waiting to close the stream, cancel closing!
|
|
1189
|
+
this.cancelClose();
|
|
929
1190
|
}
|
|
930
1191
|
/**
|
|
931
1192
|
* @inheritdoc
|
|
@@ -936,22 +1197,38 @@ class AbstractDataConnectStreamTransport extends AbstractDataConnectTransport {
|
|
|
936
1197
|
*/
|
|
937
1198
|
invokeUnsubscribe(queryName, variables) {
|
|
938
1199
|
const mapKey = this.getMapKey(queryName, variables);
|
|
939
|
-
const subscribeRequest = this.
|
|
1200
|
+
const subscribeRequest = this.activeInvokeSubscribeRequests.get(mapKey);
|
|
940
1201
|
if (!subscribeRequest) {
|
|
941
1202
|
return;
|
|
942
1203
|
}
|
|
943
1204
|
const requestId = subscribeRequest.requestId;
|
|
1205
|
+
this.subscribeObservers.delete(requestId);
|
|
1206
|
+
const resumePromise = this.resumeRequestPromises.get(requestId);
|
|
1207
|
+
if (resumePromise) {
|
|
1208
|
+
this.pendingCancellations.set(requestId, {
|
|
1209
|
+
operationName: queryName,
|
|
1210
|
+
variables
|
|
1211
|
+
});
|
|
1212
|
+
return;
|
|
1213
|
+
}
|
|
1214
|
+
this.cancelSubscription(requestId, mapKey);
|
|
1215
|
+
}
|
|
1216
|
+
/**
|
|
1217
|
+
* Cancels a subscription, cleans up the request tracking data structures, and checks to see if we
|
|
1218
|
+
* should close the stream due to inactivity.
|
|
1219
|
+
*/
|
|
1220
|
+
cancelSubscription(requestId, mapKey) {
|
|
1221
|
+
this.cleanupInvokeSubscribeRequest(requestId, mapKey);
|
|
944
1222
|
const cancelBody = {
|
|
945
1223
|
requestId,
|
|
946
1224
|
cancel: {}
|
|
947
1225
|
};
|
|
948
|
-
this.cleanupSubscribeRequest(requestId, mapKey);
|
|
949
1226
|
// asynchronous, fire and forget
|
|
950
1227
|
this.sendRequestMessage(cancelBody).catch(err => {
|
|
951
1228
|
logError(`Stream Transport failed to send unsubscribe message: ${err}`);
|
|
952
1229
|
});
|
|
953
1230
|
if (!this.hasActiveSubscriptions) {
|
|
954
|
-
this.
|
|
1231
|
+
this.startIdleCloseTimeout();
|
|
955
1232
|
}
|
|
956
1233
|
}
|
|
957
1234
|
onAuthTokenChanged(newToken) {
|
|
@@ -970,38 +1247,58 @@ class AbstractDataConnectStreamTransport extends AbstractDataConnectTransport {
|
|
|
970
1247
|
(!oldAuthUid && newAuthUid) || // user logged in
|
|
971
1248
|
(oldAuthUid && newAuthUid !== oldAuthUid) // logged in user changed
|
|
972
1249
|
) {
|
|
973
|
-
this.
|
|
974
|
-
void this.attemptClose();
|
|
1250
|
+
void this.cleanupAndTerminate(Code.UNAUTHORIZED, 'Stream disconnected due to auth change.');
|
|
975
1251
|
}
|
|
976
1252
|
}
|
|
977
1253
|
/**
|
|
978
1254
|
* Handle a response message from the server. Called by the connection-specific implementation after
|
|
979
|
-
* it's transformed a message from the server into a {@
|
|
980
|
-
* @param requestId the
|
|
1255
|
+
* it's transformed a message from the server into a {@linkcode DataConnectResponse}.
|
|
1256
|
+
* @param requestId the Request ID associated with this response.
|
|
981
1257
|
* @param response the response from the server.
|
|
982
1258
|
*/
|
|
983
1259
|
async handleResponse(requestId, response) {
|
|
984
1260
|
if (this.executeRequestPromises.has(requestId)) {
|
|
985
|
-
// don't clean up the tracking maps here, they're handled automatically when the execute promise settles
|
|
986
1261
|
const { resolveFn, rejectFn } = this.executeRequestPromises.get(requestId);
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1262
|
+
this.handleInvokeOperationResponse(resolveFn, rejectFn, response);
|
|
1263
|
+
}
|
|
1264
|
+
else if (this.subscribeObservers.has(requestId) ||
|
|
1265
|
+
this.resumeRequestPromises.has(requestId)) {
|
|
1266
|
+
const observer = this.subscribeObservers.get(requestId);
|
|
1267
|
+
const resumePromise = this.resumeRequestPromises.get(requestId);
|
|
1268
|
+
if (resumePromise) {
|
|
1269
|
+
this.resumeRequestPromises.delete(requestId);
|
|
1270
|
+
const { resolveFn, rejectFn } = resumePromise;
|
|
1271
|
+
this.handleInvokeOperationResponse(resolveFn, rejectFn, response);
|
|
994
1272
|
}
|
|
995
|
-
|
|
996
|
-
|
|
1273
|
+
if (observer) {
|
|
1274
|
+
try {
|
|
1275
|
+
await observer.onData(response);
|
|
1276
|
+
}
|
|
1277
|
+
catch (e) {
|
|
1278
|
+
logError(`Error in observer callback: ${e}`);
|
|
1279
|
+
}
|
|
997
1280
|
}
|
|
998
1281
|
}
|
|
999
|
-
else
|
|
1000
|
-
|
|
1001
|
-
|
|
1282
|
+
else {
|
|
1283
|
+
logError(`Stream response contained unrecognized requestId '${requestId}'`);
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
/**
|
|
1287
|
+
* Handles an invoke operation response, resolving or rejecting the promise returned to the user
|
|
1288
|
+
* Does not handle any cleanup for requests - this should be handled by the caller or the promise's
|
|
1289
|
+
* finally() block.
|
|
1290
|
+
*/
|
|
1291
|
+
handleInvokeOperationResponse(resolveFn, rejectFn, response) {
|
|
1292
|
+
if (response.errors && response.errors.length) {
|
|
1293
|
+
const failureResponse = {
|
|
1294
|
+
errors: response.errors,
|
|
1295
|
+
data: response.data
|
|
1296
|
+
};
|
|
1297
|
+
const stringified = JSON.stringify(response.errors);
|
|
1298
|
+
rejectFn(new DataConnectOperationError('DataConnect error while performing request: ' + stringified, failureResponse));
|
|
1002
1299
|
}
|
|
1003
1300
|
else {
|
|
1004
|
-
|
|
1301
|
+
resolveFn(response);
|
|
1005
1302
|
}
|
|
1006
1303
|
}
|
|
1007
1304
|
}
|
|
@@ -1045,6 +1342,18 @@ const WEBSOCKET_CLOSE_CODE = 1000;
|
|
|
1045
1342
|
* @internal
|
|
1046
1343
|
*/
|
|
1047
1344
|
class WebSocketTransport extends AbstractDataConnectStreamTransport {
|
|
1345
|
+
constructor() {
|
|
1346
|
+
super(...arguments);
|
|
1347
|
+
/** Decodes binary WebSocket responses to strings */
|
|
1348
|
+
this.decoder = undefined;
|
|
1349
|
+
/** The current connection to the server. Undefined if disconnected. */
|
|
1350
|
+
this.connection = undefined;
|
|
1351
|
+
/**
|
|
1352
|
+
* Current connection attempt. If null, we are not currently attemping to connect (not connected,
|
|
1353
|
+
* or already connected). Will be resolved or rejected when the connection is opened or fails to open.
|
|
1354
|
+
*/
|
|
1355
|
+
this.connectionAttempt = null;
|
|
1356
|
+
}
|
|
1048
1357
|
get endpointUrl() {
|
|
1049
1358
|
return websocketUrlBuilder({
|
|
1050
1359
|
connector: this._connectorName,
|
|
@@ -1070,24 +1379,6 @@ class WebSocketTransport extends AbstractDataConnectStreamTransport {
|
|
|
1070
1379
|
get streamIsReady() {
|
|
1071
1380
|
return this.connection?.readyState === WebSocket.OPEN;
|
|
1072
1381
|
}
|
|
1073
|
-
constructor(options, apiKey, appId, authProvider, appCheckProvider, transportOptions, _isUsingGen = false, _callerSdkType = CallerSdkTypeEnum.Base) {
|
|
1074
|
-
super(options, apiKey, appId, authProvider, appCheckProvider, transportOptions, _isUsingGen, _callerSdkType);
|
|
1075
|
-
this.apiKey = apiKey;
|
|
1076
|
-
this.appId = appId;
|
|
1077
|
-
this.authProvider = authProvider;
|
|
1078
|
-
this.appCheckProvider = appCheckProvider;
|
|
1079
|
-
this._isUsingGen = _isUsingGen;
|
|
1080
|
-
this._callerSdkType = _callerSdkType;
|
|
1081
|
-
/** Decodes binary WebSocket responses to strings */
|
|
1082
|
-
this.decoder = undefined;
|
|
1083
|
-
/** The current connection to the server. Undefined if disconnected. */
|
|
1084
|
-
this.connection = undefined;
|
|
1085
|
-
/**
|
|
1086
|
-
* Current connection attempt. If null, we are not currently attemping to connect (not connected,
|
|
1087
|
-
* or already connected). Will be resolved or rejected when the connection is opened or fails to open.
|
|
1088
|
-
*/
|
|
1089
|
-
this.connectionAttempt = null;
|
|
1090
|
-
}
|
|
1091
1382
|
ensureConnection() {
|
|
1092
1383
|
try {
|
|
1093
1384
|
if (this.streamIsReady) {
|
|
@@ -1266,7 +1557,7 @@ class WebSocketTransport extends AbstractDataConnectStreamTransport {
|
|
|
1266
1557
|
}
|
|
1267
1558
|
|
|
1268
1559
|
const name = "@firebase/data-connect";
|
|
1269
|
-
const version = "0.
|
|
1560
|
+
const version = "0.7.0";
|
|
1270
1561
|
|
|
1271
1562
|
/**
|
|
1272
1563
|
* @license
|
|
@@ -2455,7 +2746,7 @@ class DataConnectTransportManager {
|
|
|
2455
2746
|
if (this.isUsingEmulator && this.transportOptions) {
|
|
2456
2747
|
this.streamTransport.useEmulator(this.transportOptions.host, this.transportOptions.port, this.transportOptions.sslEnabled);
|
|
2457
2748
|
}
|
|
2458
|
-
this.streamTransport.
|
|
2749
|
+
this.streamTransport.onCloseCallback = () => {
|
|
2459
2750
|
this.streamTransport = undefined;
|
|
2460
2751
|
};
|
|
2461
2752
|
}
|