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