@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.esm.js
CHANGED
|
@@ -4,7 +4,7 @@ import { FirebaseError, generateSHA256Hash, isCloudWorkstation, pingServer } fro
|
|
|
4
4
|
import { Logger } from '@firebase/logger';
|
|
5
5
|
|
|
6
6
|
const name = "@firebase/data-connect";
|
|
7
|
-
const version = "0.
|
|
7
|
+
const version = "0.7.0";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* @license
|
|
@@ -1660,49 +1660,128 @@ class RESTTransport extends AbstractDataConnectTransport {
|
|
|
1660
1660
|
* See the License for the specific language governing permissions and
|
|
1661
1661
|
* limitations under the License.
|
|
1662
1662
|
*/
|
|
1663
|
-
/** The
|
|
1663
|
+
/** The Request ID of the first request over the stream */
|
|
1664
1664
|
const FIRST_REQUEST_ID = 1;
|
|
1665
|
-
/** Time to wait before closing an idle connection (no active subscriptions) */
|
|
1665
|
+
/** Time to wait before closing an idle connection (no active subscriptions). */
|
|
1666
1666
|
const IDLE_CONNECTION_TIMEOUT_MS = 60 * 1000; // 1 minute
|
|
1667
|
+
/** Initial reconnect delay in ms */
|
|
1668
|
+
const INITIAL_RECONNECT_DELAY_MS = 1000;
|
|
1669
|
+
/** Max reconnect delay in ms */
|
|
1670
|
+
const MAX_RECONNECT_DELAY_MS = 30000;
|
|
1671
|
+
/** Max random jitter to add to reconnect delay in ms */
|
|
1672
|
+
const MAX_RECONNECT_JITTER_MS = 500;
|
|
1673
|
+
/** Factor to multiply delay by on failure */
|
|
1674
|
+
const RECONNECT_BACKOFF_FACTOR = 1.3;
|
|
1675
|
+
/** Max number of reconnection attempts before giving up */
|
|
1676
|
+
const MAX_RECONNECT_ATTEMPTS = 10;
|
|
1667
1677
|
/**
|
|
1668
|
-
* The base class for all
|
|
1669
|
-
* Handles management of logical streams (requests), authentication, data routing to query layer,
|
|
1678
|
+
* The base class for all Stream Transport implementations.
|
|
1679
|
+
* Handles management of logical streams (requests), authentication, data routing to query layer,
|
|
1680
|
+
* request optimizations, etc.
|
|
1670
1681
|
* @internal
|
|
1671
1682
|
*/
|
|
1672
1683
|
class AbstractDataConnectStreamTransport extends AbstractDataConnectTransport {
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
this.
|
|
1684
|
+
/** Is the stream currently waiting to close connection? */
|
|
1685
|
+
get isPendingClose() {
|
|
1686
|
+
return !!this.idleTimeout;
|
|
1687
|
+
}
|
|
1688
|
+
/** True if there are active subscriptions on the stream */
|
|
1689
|
+
get hasActiveSubscriptions() {
|
|
1690
|
+
return this.activeInvokeSubscribeRequests.size > 0;
|
|
1691
|
+
}
|
|
1692
|
+
/** True if there are active execute or mutation requests on the stream */
|
|
1693
|
+
get hasActiveExecuteRequests() {
|
|
1694
|
+
return (this.activeInvokeQueryRequests.size > 0 ||
|
|
1695
|
+
this.activeInvokeMutationRequests.size > 0);
|
|
1696
|
+
}
|
|
1697
|
+
constructor(options, apiKey, appId, authProvider, appCheckProvider, transportOptions, _isUsingGen = false, _callerSdkType = CallerSdkTypeEnum.Base) {
|
|
1698
|
+
super(options, apiKey, appId, authProvider, appCheckProvider, transportOptions, _isUsingGen, _callerSdkType);
|
|
1699
|
+
this.apiKey = apiKey;
|
|
1700
|
+
this.appId = appId;
|
|
1701
|
+
this.authProvider = authProvider;
|
|
1702
|
+
this.appCheckProvider = appCheckProvider;
|
|
1703
|
+
this._isUsingGen = _isUsingGen;
|
|
1704
|
+
this._callerSdkType = _callerSdkType;
|
|
1676
1705
|
/** True if the transport is unable to connect to the server */
|
|
1677
1706
|
this.isUnableToConnect = false;
|
|
1678
|
-
/** The
|
|
1707
|
+
/** The Request ID of the next message to be sent. Monotonically increasing sequence number starting at {@linkcode FIRST_REQUEST_ID}. */
|
|
1679
1708
|
this.requestNumber = FIRST_REQUEST_ID;
|
|
1680
1709
|
/**
|
|
1681
|
-
* Map of query/variables to their active
|
|
1710
|
+
* Map of query/variables to their active {@linkcode ExecuteStreamRequest} or {@linkcode ResumeStreamRequest}
|
|
1711
|
+
* request bodies. These requests are de-duplicated by query/variables so that there is only one active
|
|
1712
|
+
* request for each query/variables combination.
|
|
1682
1713
|
*/
|
|
1683
|
-
this.
|
|
1714
|
+
this.activeInvokeQueryRequests = new Map();
|
|
1684
1715
|
/**
|
|
1685
|
-
* Map of
|
|
1716
|
+
* Map of query/variables to the promises returned to the user, for invokeQuery requests which are
|
|
1717
|
+
* queued and waiting for active request to resolve.
|
|
1686
1718
|
*/
|
|
1687
|
-
this.
|
|
1719
|
+
this.queuedInvokeQueryRequests = new Map();
|
|
1688
1720
|
/**
|
|
1689
|
-
* Map of
|
|
1721
|
+
* Map of mutation/variables to their active {@linkcode ExecuteStreamRequest} request bodies. Mutations
|
|
1722
|
+
* can have more than one active request at a time as they are not idempotent, and therefore should
|
|
1723
|
+
* not be de-duplicated.
|
|
1690
1724
|
*/
|
|
1691
|
-
this.
|
|
1725
|
+
this.activeInvokeMutationRequests = new Map();
|
|
1692
1726
|
/**
|
|
1693
|
-
* Map of
|
|
1727
|
+
* Map of query/variables to their active {@linkcode SubscribeStreamRequest} request bodies. There
|
|
1728
|
+
* may only be one active request for each query/variables combination.
|
|
1729
|
+
*/
|
|
1730
|
+
this.activeInvokeSubscribeRequests = new Map();
|
|
1731
|
+
/**
|
|
1732
|
+
* Map of active {@linkcode ExecuteStreamRequest} RequestIds from {@linkcode invokeQuery} and {@linkcode invokeMutation},
|
|
1733
|
+
* and their corresponding {@linkcode InvokeOperationPromise}.
|
|
1694
1734
|
*/
|
|
1695
1735
|
this.executeRequestPromises = new Map();
|
|
1696
1736
|
/**
|
|
1697
|
-
* Map of active
|
|
1737
|
+
* Map of active {@linkcode ResumeStreamRequest} RequestIds from {@linkcode invokeQuery}, and their
|
|
1738
|
+
* corresponding {@linkcode InvokeOperationPromise}.
|
|
1739
|
+
*/
|
|
1740
|
+
this.resumeRequestPromises = new Map();
|
|
1741
|
+
/**
|
|
1742
|
+
* Map of active {@linkcode invokeSubscribe} RequestIds and their corresponding {@linkcode SubscribeObserver}.
|
|
1698
1743
|
*/
|
|
1699
1744
|
this.subscribeObservers = new Map();
|
|
1700
|
-
/**
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1745
|
+
/**
|
|
1746
|
+
* Map of subscribe RequestIds to deferred unsubscription requests. Used when a client unsubscribes
|
|
1747
|
+
* while a resume request is actively pending.
|
|
1748
|
+
*/
|
|
1749
|
+
this.pendingCancellations = new Map();
|
|
1750
|
+
/** current idle timeout, if any */
|
|
1751
|
+
this.idleTimeout = null;
|
|
1704
1752
|
/** Flag to ensure we wait for the initial auth state once per connection attempt. */
|
|
1705
1753
|
this.hasWaitedForInitialAuth = false;
|
|
1754
|
+
/** Delay for next reconnection attempt in ms */
|
|
1755
|
+
this.reconnectDelayMs = INITIAL_RECONNECT_DELAY_MS;
|
|
1756
|
+
/** Timer for reconnection */
|
|
1757
|
+
this.reconnectTimer = null;
|
|
1758
|
+
/** Number of consecutive reconnection attempts */
|
|
1759
|
+
this.reconnectAttempts = 0;
|
|
1760
|
+
/** Callback to remove online event listener */
|
|
1761
|
+
this.removeOnlineEventListener = null;
|
|
1762
|
+
/** Callback to remove visibility change event listener */
|
|
1763
|
+
this.removeVisibilityChangeEventListener = null;
|
|
1764
|
+
/**
|
|
1765
|
+
* Short-circuit a reconnection attempt, if one is pending. Triggered when an online event is
|
|
1766
|
+
* dispatched.
|
|
1767
|
+
*/
|
|
1768
|
+
this.onOnlineEventListener = () => {
|
|
1769
|
+
if (this.reconnectTimer) {
|
|
1770
|
+
this.cancelReconnect();
|
|
1771
|
+
void this.attemptReconnect();
|
|
1772
|
+
}
|
|
1773
|
+
};
|
|
1774
|
+
/**
|
|
1775
|
+
* Short-circuit a reconnection attempt, if one is pending. Triggered when a visibility change
|
|
1776
|
+
* event is dispatched.
|
|
1777
|
+
*/
|
|
1778
|
+
this.onVisibilityChangeEventListener = () => {
|
|
1779
|
+
const doc = globalThis.document;
|
|
1780
|
+
if (doc && doc.visibilityState === 'visible' && this.reconnectTimer) {
|
|
1781
|
+
this.cancelReconnect();
|
|
1782
|
+
void this.attemptReconnect();
|
|
1783
|
+
}
|
|
1784
|
+
};
|
|
1706
1785
|
/**
|
|
1707
1786
|
* Tracks if the next message to be sent is the first message of the stream.
|
|
1708
1787
|
*/
|
|
@@ -1712,62 +1791,62 @@ class AbstractDataConnectStreamTransport extends AbstractDataConnectTransport {
|
|
|
1712
1791
|
* Used to detect if the token has changed and needs to be resent.
|
|
1713
1792
|
*/
|
|
1714
1793
|
this.lastSentAuthToken = null;
|
|
1794
|
+
this.registerBrowserEventListeners();
|
|
1715
1795
|
}
|
|
1716
|
-
/**
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1796
|
+
/**
|
|
1797
|
+
* Register event listeners for browser-specific events like online/offline and visibility changes.
|
|
1798
|
+
*/
|
|
1799
|
+
registerBrowserEventListeners() {
|
|
1800
|
+
if ('addEventListener' in globalThis) {
|
|
1801
|
+
const listener = this.onOnlineEventListener;
|
|
1802
|
+
globalThis.addEventListener('online', listener);
|
|
1803
|
+
this.removeOnlineEventListener = () => globalThis.removeEventListener('online', listener);
|
|
1804
|
+
}
|
|
1805
|
+
const doc = globalThis.document;
|
|
1806
|
+
if (doc && 'addEventListener' in doc) {
|
|
1807
|
+
const listener = this.onVisibilityChangeEventListener;
|
|
1808
|
+
doc.addEventListener('visibilitychange', listener);
|
|
1809
|
+
this.removeVisibilityChangeEventListener = () => doc.removeEventListener('visibilitychange', listener);
|
|
1810
|
+
}
|
|
1723
1811
|
}
|
|
1724
|
-
/**
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1812
|
+
/**
|
|
1813
|
+
* Remove event listeners registered by {@linkcode AbstractDataConnectStreamTransport.registerBrowserEventListeners | registerBrowserEventListeners()}
|
|
1814
|
+
* for browser-specific events like online/offline and visibility changes.
|
|
1815
|
+
*/
|
|
1816
|
+
cleanupBrowserEventListeners() {
|
|
1817
|
+
this.removeVisibilityChangeEventListener?.();
|
|
1818
|
+
this.removeVisibilityChangeEventListener = null;
|
|
1819
|
+
this.removeOnlineEventListener?.();
|
|
1820
|
+
this.removeOnlineEventListener = null;
|
|
1728
1821
|
}
|
|
1729
1822
|
/**
|
|
1730
|
-
*
|
|
1823
|
+
* Disposes of the transport instance, cleaning up event listeners and timers,
|
|
1824
|
+
* and closing the connection.
|
|
1731
1825
|
*/
|
|
1732
|
-
|
|
1733
|
-
|
|
1826
|
+
async cleanupAndTerminate(code, reason) {
|
|
1827
|
+
this.cleanupBrowserEventListeners();
|
|
1828
|
+
this.cancelReconnect();
|
|
1829
|
+
this.cancelClose();
|
|
1830
|
+
this.rejectAllRequests(code ?? Code.OTHER, reason ?? 'Stream disposed.');
|
|
1831
|
+
await this.closeConnection();
|
|
1832
|
+
this.onCloseCallback?.();
|
|
1734
1833
|
}
|
|
1735
1834
|
/**
|
|
1736
|
-
*
|
|
1737
|
-
*
|
|
1738
|
-
* @returns The reject function and the response promise.
|
|
1739
|
-
*
|
|
1740
|
-
* @remarks
|
|
1741
|
-
* This method returns a promise, but is synchronous.
|
|
1835
|
+
* Generates and returns the next Request ID. Starts at {@linkcode FIRST_REQUEST_ID} and increments
|
|
1836
|
+
* for each request sent.
|
|
1742
1837
|
*/
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
let resolveFn;
|
|
1746
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1747
|
-
let rejectFn;
|
|
1748
|
-
const responsePromise = new Promise((resolve, reject) => {
|
|
1749
|
-
resolveFn = resolve;
|
|
1750
|
-
rejectFn = reject;
|
|
1751
|
-
});
|
|
1752
|
-
const executeRequestPromise = {
|
|
1753
|
-
responsePromise,
|
|
1754
|
-
resolveFn: resolveFn,
|
|
1755
|
-
rejectFn: rejectFn
|
|
1756
|
-
};
|
|
1757
|
-
this.activeQueryExecuteRequests.set(mapKey, executeBody);
|
|
1758
|
-
this.executeRequestPromises.set(requestId, executeRequestPromise);
|
|
1759
|
-
return executeRequestPromise;
|
|
1838
|
+
nextRequestId() {
|
|
1839
|
+
return (this.requestNumber++).toString();
|
|
1760
1840
|
}
|
|
1761
1841
|
/**
|
|
1762
|
-
* Tracks
|
|
1763
|
-
* that will be resolved when the response is received.
|
|
1764
|
-
* @returns The
|
|
1842
|
+
* Tracks an {@linkcode invokeMutation} request, storing the request body and creating and storing a
|
|
1843
|
+
* response promise that will be resolved when the response is received.
|
|
1844
|
+
* @returns The tracked {@linkcode InvokeOperationPromise}.
|
|
1765
1845
|
*
|
|
1766
1846
|
* @remarks
|
|
1767
1847
|
* This method returns a promise, but is synchronous.
|
|
1768
1848
|
*/
|
|
1769
|
-
|
|
1770
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1849
|
+
trackInvokeMutationRequest(requestId, mapKey, executeBody) {
|
|
1771
1850
|
let resolveFn;
|
|
1772
1851
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1773
1852
|
let rejectFn;
|
|
@@ -1780,42 +1859,43 @@ class AbstractDataConnectStreamTransport extends AbstractDataConnectTransport {
|
|
|
1780
1859
|
resolveFn: resolveFn,
|
|
1781
1860
|
rejectFn: rejectFn
|
|
1782
1861
|
};
|
|
1783
|
-
const activeRequests = this.
|
|
1862
|
+
const activeRequests = this.activeInvokeMutationRequests.get(mapKey) || [];
|
|
1784
1863
|
activeRequests.push(executeBody);
|
|
1785
|
-
this.
|
|
1864
|
+
this.activeInvokeMutationRequests.set(mapKey, activeRequests);
|
|
1786
1865
|
this.executeRequestPromises.set(requestId, executeRequestPromise);
|
|
1787
1866
|
return executeRequestPromise;
|
|
1788
1867
|
}
|
|
1789
1868
|
/**
|
|
1790
|
-
* Tracks
|
|
1869
|
+
* Tracks an {@linkcode invokeSubscribe} request, storing the request body and the {@linkcode SubscribeObserver}.
|
|
1791
1870
|
* @remarks
|
|
1792
1871
|
* This method is synchronous.
|
|
1793
1872
|
*/
|
|
1794
|
-
|
|
1795
|
-
this.
|
|
1873
|
+
trackInvokeSubscribeRequest(requestId, mapKey, subscribeBody, observer) {
|
|
1874
|
+
this.activeInvokeSubscribeRequests.set(mapKey, subscribeBody);
|
|
1796
1875
|
this.subscribeObservers.set(requestId, observer);
|
|
1797
1876
|
}
|
|
1798
1877
|
/**
|
|
1799
1878
|
* Cleans up the query execute request tracking data structures, deleting the tracked request and
|
|
1800
1879
|
* it's associated promise.
|
|
1801
1880
|
*/
|
|
1802
|
-
|
|
1803
|
-
this.
|
|
1881
|
+
cleanupInvokeQueryRequest(requestId, mapKey) {
|
|
1882
|
+
this.activeInvokeQueryRequests.delete(mapKey);
|
|
1804
1883
|
this.executeRequestPromises.delete(requestId);
|
|
1884
|
+
this.resumeRequestPromises.delete(requestId);
|
|
1805
1885
|
}
|
|
1806
1886
|
/**
|
|
1807
1887
|
* Cleans up the mutation execute request tracking data structures, deleting the tracked request and
|
|
1808
1888
|
* it's associated promise.
|
|
1809
1889
|
*/
|
|
1810
|
-
|
|
1811
|
-
const executeRequests = this.
|
|
1890
|
+
cleanupInvokeMutationRequest(requestId, mapKey) {
|
|
1891
|
+
const executeRequests = this.activeInvokeMutationRequests.get(mapKey);
|
|
1812
1892
|
if (executeRequests) {
|
|
1813
|
-
const updatedRequests = executeRequests.filter(
|
|
1893
|
+
const updatedRequests = executeRequests.filter(request => request.requestId !== requestId);
|
|
1814
1894
|
if (updatedRequests.length > 0) {
|
|
1815
|
-
this.
|
|
1895
|
+
this.activeInvokeMutationRequests.set(mapKey, updatedRequests);
|
|
1816
1896
|
}
|
|
1817
1897
|
else {
|
|
1818
|
-
this.
|
|
1898
|
+
this.activeInvokeMutationRequests.delete(mapKey);
|
|
1819
1899
|
}
|
|
1820
1900
|
}
|
|
1821
1901
|
this.executeRequestPromises.delete(requestId);
|
|
@@ -1824,10 +1904,72 @@ class AbstractDataConnectStreamTransport extends AbstractDataConnectTransport {
|
|
|
1824
1904
|
* Cleans up the subscribe request tracking data structures, deleting the tracked request and
|
|
1825
1905
|
* it's associated promise.
|
|
1826
1906
|
*/
|
|
1827
|
-
|
|
1828
|
-
this.
|
|
1907
|
+
cleanupInvokeSubscribeRequest(requestId, mapKey) {
|
|
1908
|
+
this.activeInvokeSubscribeRequests.delete(mapKey);
|
|
1829
1909
|
this.subscribeObservers.delete(requestId);
|
|
1830
1910
|
}
|
|
1911
|
+
/**
|
|
1912
|
+
* Cancel reconnecting.
|
|
1913
|
+
*/
|
|
1914
|
+
cancelReconnect() {
|
|
1915
|
+
if (this.reconnectTimer) {
|
|
1916
|
+
clearTimeout(this.reconnectTimer);
|
|
1917
|
+
this.reconnectTimer = null;
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
/**
|
|
1921
|
+
* Starts the backoff timer for reconnection attempts. We use an exponential backoff with randomized
|
|
1922
|
+
* jitter to prevent overwhelming the backend with connection attempts.
|
|
1923
|
+
*/
|
|
1924
|
+
startReconnectBackoff() {
|
|
1925
|
+
if (this.reconnectTimer) {
|
|
1926
|
+
return;
|
|
1927
|
+
}
|
|
1928
|
+
if (this.reconnectAttempts++ >= MAX_RECONNECT_ATTEMPTS) {
|
|
1929
|
+
const errorString = 'Stream disconnected and could not reconnect - max stream reconnection attempts reached.';
|
|
1930
|
+
logError(errorString);
|
|
1931
|
+
void this.cleanupAndTerminate(Code.OTHER, errorString);
|
|
1932
|
+
return;
|
|
1933
|
+
}
|
|
1934
|
+
const delay = this.reconnectDelayMs;
|
|
1935
|
+
this.reconnectDelayMs = Math.min(this.reconnectDelayMs * RECONNECT_BACKOFF_FACTOR, MAX_RECONNECT_DELAY_MS);
|
|
1936
|
+
const jitter = Math.random() * MAX_RECONNECT_JITTER_MS;
|
|
1937
|
+
this.reconnectTimer = setTimeout(() => {
|
|
1938
|
+
this.reconnectTimer = null;
|
|
1939
|
+
void this.attemptReconnect();
|
|
1940
|
+
}, delay + jitter);
|
|
1941
|
+
}
|
|
1942
|
+
async attemptReconnect() {
|
|
1943
|
+
try {
|
|
1944
|
+
await this.ensureConnection();
|
|
1945
|
+
// reset on success
|
|
1946
|
+
this.reconnectDelayMs = INITIAL_RECONNECT_DELAY_MS;
|
|
1947
|
+
this.reconnectAttempts = 0;
|
|
1948
|
+
await this.retriggerActiveRequests();
|
|
1949
|
+
}
|
|
1950
|
+
catch (e) {
|
|
1951
|
+
if (e instanceof FirebaseError) {
|
|
1952
|
+
logDebug(`Reconnect attempt #${this.reconnectAttempts} failed with Firebase error: ${e.message}. Retrying...`);
|
|
1953
|
+
this.startReconnectBackoff();
|
|
1954
|
+
}
|
|
1955
|
+
else {
|
|
1956
|
+
logError(`Unexpected error during reconnect attempt #${this.reconnectAttempts}: ${e}`);
|
|
1957
|
+
void this.cleanupAndTerminate(Code.OTHER, `Unexpected error during reconnect attempt #${this.reconnectAttempts}: ${e}`);
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
/**
|
|
1962
|
+
* Retriggers all active requests on the stream connection - first subscribes, then query executions,
|
|
1963
|
+
* and skip mutations. Used after a successful reconnection.
|
|
1964
|
+
*/
|
|
1965
|
+
async retriggerActiveRequests() {
|
|
1966
|
+
for (const [_, subscribeBody] of this.activeInvokeSubscribeRequests) {
|
|
1967
|
+
await this.sendRequestMessage(subscribeBody);
|
|
1968
|
+
}
|
|
1969
|
+
for (const [_, requestBody] of this.activeInvokeQueryRequests) {
|
|
1970
|
+
await this.sendRequestMessage(requestBody);
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1831
1973
|
/**
|
|
1832
1974
|
* Indicates whether we should include the auth token in the next message.
|
|
1833
1975
|
* Only true if there is an auth token and it is different from the last sent auth token, or this
|
|
@@ -1846,66 +1988,91 @@ class AbstractDataConnectStreamTransport extends AbstractDataConnectTransport {
|
|
|
1846
1988
|
this.hasWaitedForInitialAuth = false;
|
|
1847
1989
|
}
|
|
1848
1990
|
/**
|
|
1849
|
-
*
|
|
1850
|
-
*
|
|
1991
|
+
* Begin closing the connection. Waits for {@linkcode IDLE_CONNECTION_TIMEOUT_MS} without cleaning up
|
|
1992
|
+
* any requests (meaning it will not close after the timeout unless the requests are closed first).
|
|
1993
|
+
* This is a graceful close - it will be called when there are no more active subscriptions, so
|
|
1994
|
+
* there's no need to cleanup.
|
|
1851
1995
|
*/
|
|
1852
|
-
|
|
1853
|
-
if (this.
|
|
1996
|
+
startIdleCloseTimeout() {
|
|
1997
|
+
if (this.idleTimeout) {
|
|
1854
1998
|
return;
|
|
1855
1999
|
}
|
|
1856
|
-
this.
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
* no more active subscriptions, so there's no need to cleanup.
|
|
1864
|
-
*/
|
|
1865
|
-
prepareToCloseGracefully() {
|
|
1866
|
-
if (this.pendingClose) {
|
|
1867
|
-
return;
|
|
1868
|
-
}
|
|
1869
|
-
this.pendingClose = true;
|
|
1870
|
-
this.closeTimeoutFinished = false;
|
|
1871
|
-
this.closeTimeout = setTimeout(() => {
|
|
1872
|
-
this.closeTimeoutFinished = true;
|
|
1873
|
-
void this.attemptClose();
|
|
2000
|
+
this.idleTimeout = setTimeout(() => {
|
|
2001
|
+
this.idleTimeout = null;
|
|
2002
|
+
// Safety check: Don't close if new requests arrived during the timeout!
|
|
2003
|
+
if (this.hasActiveSubscriptions || this.hasActiveExecuteRequests) {
|
|
2004
|
+
return;
|
|
2005
|
+
}
|
|
2006
|
+
void this.cleanupAndTerminate(Code.OTHER, 'Stream closed due to idleness.');
|
|
1874
2007
|
}, IDLE_CONNECTION_TIMEOUT_MS);
|
|
1875
2008
|
}
|
|
1876
2009
|
/**
|
|
1877
2010
|
* Cancel closing the connection.
|
|
1878
2011
|
*/
|
|
1879
2012
|
cancelClose() {
|
|
1880
|
-
if (this.
|
|
1881
|
-
clearTimeout(this.
|
|
2013
|
+
if (this.idleTimeout) {
|
|
2014
|
+
clearTimeout(this.idleTimeout);
|
|
2015
|
+
this.idleTimeout = null;
|
|
1882
2016
|
}
|
|
1883
|
-
this.pendingClose = false;
|
|
1884
|
-
this.closeTimeoutFinished = false;
|
|
1885
2017
|
}
|
|
1886
2018
|
/**
|
|
1887
2019
|
* Reject all active execute promises and notify all subscribe observers with the given error.
|
|
1888
2020
|
* Clear active request tracking maps without cancelling or re-invoking any requests.
|
|
1889
2021
|
*/
|
|
1890
|
-
|
|
1891
|
-
this.
|
|
1892
|
-
this.
|
|
1893
|
-
this.
|
|
2022
|
+
rejectAllRequests(code, reason) {
|
|
2023
|
+
this.activeInvokeQueryRequests.clear();
|
|
2024
|
+
this.activeInvokeMutationRequests.clear();
|
|
2025
|
+
this.activeInvokeSubscribeRequests.clear();
|
|
1894
2026
|
const error = new DataConnectError(code, reason);
|
|
2027
|
+
for (const [mapKey, { rejectFn }] of this.queuedInvokeQueryRequests) {
|
|
2028
|
+
this.queuedInvokeQueryRequests.delete(mapKey);
|
|
2029
|
+
rejectFn(error);
|
|
2030
|
+
}
|
|
1895
2031
|
for (const [requestId, { rejectFn }] of this.executeRequestPromises) {
|
|
1896
2032
|
this.executeRequestPromises.delete(requestId);
|
|
1897
2033
|
rejectFn(error);
|
|
1898
2034
|
}
|
|
2035
|
+
for (const [requestId, { rejectFn }] of this.resumeRequestPromises) {
|
|
2036
|
+
this.resumeRequestPromises.delete(requestId);
|
|
2037
|
+
rejectFn(error);
|
|
2038
|
+
}
|
|
1899
2039
|
for (const [requestId, observer] of this.subscribeObservers) {
|
|
1900
2040
|
this.subscribeObservers.delete(requestId);
|
|
1901
2041
|
observer.onDisconnect(code, reason);
|
|
1902
2042
|
}
|
|
2043
|
+
this.pendingCancellations.clear();
|
|
2044
|
+
this.cancelReconnect();
|
|
2045
|
+
}
|
|
2046
|
+
/**
|
|
2047
|
+
* Reject all mutation execute promises.
|
|
2048
|
+
* Clear active request tracking maps without cancelling or re-invoking any requests.
|
|
2049
|
+
*/
|
|
2050
|
+
rejectAllMutationsOnReconnect() {
|
|
2051
|
+
const error = new DataConnectError(Code.OTHER, 'Mutation aborted due to stream disconnect.');
|
|
2052
|
+
for (const [_, requests] of this.activeInvokeMutationRequests) {
|
|
2053
|
+
for (const request of requests) {
|
|
2054
|
+
const promise = this.executeRequestPromises.get(request.requestId);
|
|
2055
|
+
if (promise) {
|
|
2056
|
+
promise.rejectFn(error);
|
|
2057
|
+
this.executeRequestPromises.delete(request.requestId);
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
this.activeInvokeMutationRequests.clear();
|
|
1903
2062
|
}
|
|
1904
2063
|
/**
|
|
1905
2064
|
* Called by concrete implementations when the stream is successfully closed, gracefully or otherwise.
|
|
1906
2065
|
*/
|
|
1907
2066
|
onStreamClose(code, reason) {
|
|
1908
|
-
this.
|
|
2067
|
+
this.cancelClose();
|
|
2068
|
+
if (!this.hasActiveSubscriptions) {
|
|
2069
|
+
// skip reconnection if there are no active subscriptions
|
|
2070
|
+
void this.cleanupAndTerminate(Code.OTHER, `Stream disconnected while idle with code ${code}: ${reason}`);
|
|
2071
|
+
return;
|
|
2072
|
+
}
|
|
2073
|
+
logDebug(`Stream disconnected with code ${code}: ${reason}. Attempting reconnect...`);
|
|
2074
|
+
this.rejectAllMutationsOnReconnect();
|
|
2075
|
+
this.startReconnectBackoff();
|
|
1909
2076
|
}
|
|
1910
2077
|
/**
|
|
1911
2078
|
* Prepares a stream request message by adding necessary headers and metadata.
|
|
@@ -1937,7 +2104,6 @@ class AbstractDataConnectStreamTransport extends AbstractDataConnectTransport {
|
|
|
1937
2104
|
this.isFirstStreamMessage = false;
|
|
1938
2105
|
return preparedRequestBody;
|
|
1939
2106
|
}
|
|
1940
|
-
// TODO(stephenarosaj): just make this async
|
|
1941
2107
|
/**
|
|
1942
2108
|
* Sends a request message to the server via the concrete implementation.
|
|
1943
2109
|
* Ensures the connection is ready and prepares the message before sending.
|
|
@@ -1958,7 +2124,7 @@ class AbstractDataConnectStreamTransport extends AbstractDataConnectTransport {
|
|
|
1958
2124
|
});
|
|
1959
2125
|
}
|
|
1960
2126
|
/**
|
|
1961
|
-
* Helper to generate a consistent string key for the tracking maps.
|
|
2127
|
+
* Helper to generate a consistent string key for the request tracking maps.
|
|
1962
2128
|
*/
|
|
1963
2129
|
getMapKey(operationName, variables) {
|
|
1964
2130
|
const sortedVariables = this.sortObjectKeys(variables);
|
|
@@ -1987,28 +2153,114 @@ class AbstractDataConnectStreamTransport extends AbstractDataConnectTransport {
|
|
|
1987
2153
|
* preserves the synchronous update of the tracking data structures before the method returns.
|
|
1988
2154
|
*/
|
|
1989
2155
|
invokeQuery(queryName, variables) {
|
|
1990
|
-
const requestId = this.nextRequestId();
|
|
1991
|
-
const activeRequestKey = { operationName: queryName, variables };
|
|
1992
2156
|
const mapKey = this.getMapKey(queryName, variables);
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
2157
|
+
if (this.activeInvokeQueryRequests.has(mapKey)) {
|
|
2158
|
+
return this.queueInvokeQueryRequest(mapKey);
|
|
2159
|
+
}
|
|
2160
|
+
return this.executeOrResumeQuery(queryName, variables, mapKey);
|
|
2161
|
+
}
|
|
2162
|
+
/**
|
|
2163
|
+
* Queue a new query execute request to be executed after the currently active query execute
|
|
2164
|
+
* request resolves, and track + return a promise associated with the queued request. If there is
|
|
2165
|
+
* already a queued request for this mapKey, return the existing queued request's promise instead.
|
|
2166
|
+
*/
|
|
2167
|
+
queueInvokeQueryRequest(mapKey) {
|
|
2168
|
+
const existingQueued = this.queuedInvokeQueryRequests.get(mapKey);
|
|
2169
|
+
if (existingQueued) {
|
|
2170
|
+
// only queue one request per mapKey - return existing queued request promise
|
|
2171
|
+
return existingQueued.responsePromise;
|
|
2172
|
+
}
|
|
2173
|
+
let resolveFn;
|
|
2174
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2175
|
+
let rejectFn;
|
|
2176
|
+
const responsePromise = new Promise((resolve, reject) => {
|
|
2177
|
+
resolveFn = resolve;
|
|
2178
|
+
rejectFn = reject;
|
|
2179
|
+
});
|
|
2180
|
+
this.queuedInvokeQueryRequests.set(mapKey, {
|
|
2181
|
+
responsePromise,
|
|
2182
|
+
resolveFn: resolveFn,
|
|
2183
|
+
rejectFn: rejectFn
|
|
2184
|
+
});
|
|
2185
|
+
return responsePromise;
|
|
2186
|
+
}
|
|
2187
|
+
/**
|
|
2188
|
+
* Executes or resumes a query. Does not check for any active requests which may be overwritten by
|
|
2189
|
+
* this request - this should be handled by the caller.
|
|
2190
|
+
*/
|
|
2191
|
+
executeOrResumeQuery(queryName, variables, mapKey, queuedInvokeOperationPromise) {
|
|
2192
|
+
const activeSubscription = this.activeInvokeSubscribeRequests.get(mapKey);
|
|
2193
|
+
let resolveFn;
|
|
2194
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2195
|
+
let rejectFn;
|
|
2196
|
+
let responsePromise;
|
|
2197
|
+
// track the existing queued promise if one exists - otherwise create a new one
|
|
2198
|
+
if (queuedInvokeOperationPromise) {
|
|
2199
|
+
resolveFn = queuedInvokeOperationPromise.resolveFn;
|
|
2200
|
+
rejectFn = queuedInvokeOperationPromise.rejectFn;
|
|
2201
|
+
responsePromise = queuedInvokeOperationPromise.responsePromise;
|
|
2202
|
+
}
|
|
2203
|
+
else {
|
|
2204
|
+
responsePromise = new Promise((resolve, reject) => {
|
|
2205
|
+
resolveFn = resolve;
|
|
2206
|
+
rejectFn = reject;
|
|
2207
|
+
});
|
|
2208
|
+
}
|
|
2209
|
+
let requestId;
|
|
2210
|
+
let requestBody;
|
|
2211
|
+
if (activeSubscription) {
|
|
2212
|
+
// resume!
|
|
2213
|
+
requestId = activeSubscription.requestId;
|
|
2214
|
+
requestBody = { requestId, resume: {} };
|
|
2215
|
+
this.resumeRequestPromises.set(requestId, {
|
|
2216
|
+
responsePromise,
|
|
2217
|
+
resolveFn: resolveFn,
|
|
2218
|
+
rejectFn: rejectFn
|
|
2219
|
+
});
|
|
2220
|
+
}
|
|
2221
|
+
else {
|
|
2222
|
+
// execute!
|
|
2223
|
+
requestId = this.nextRequestId();
|
|
2224
|
+
requestBody = {
|
|
2225
|
+
requestId,
|
|
2226
|
+
execute: { operationName: queryName, variables }
|
|
2227
|
+
};
|
|
2228
|
+
this.executeRequestPromises.set(requestId, {
|
|
2229
|
+
responsePromise,
|
|
2230
|
+
resolveFn: resolveFn,
|
|
2231
|
+
rejectFn: rejectFn
|
|
2232
|
+
});
|
|
2233
|
+
}
|
|
2234
|
+
this.activeInvokeQueryRequests.set(mapKey, requestBody);
|
|
1998
2235
|
responsePromise = responsePromise.finally(() => {
|
|
1999
|
-
this.
|
|
2000
|
-
if (!this.hasActiveSubscriptions &&
|
|
2001
|
-
!this.hasActiveExecuteRequests &&
|
|
2002
|
-
this.closeTimeoutFinished) {
|
|
2003
|
-
void this.attemptClose();
|
|
2004
|
-
}
|
|
2236
|
+
this.onInvokeQueryRequestFulfilled(queryName, variables, mapKey, requestId);
|
|
2005
2237
|
});
|
|
2006
|
-
|
|
2007
|
-
this.sendRequestMessage(executeBody).catch(err => {
|
|
2238
|
+
this.sendRequestMessage(requestBody).catch(err => {
|
|
2008
2239
|
rejectFn(err);
|
|
2009
2240
|
});
|
|
2010
2241
|
return responsePromise;
|
|
2011
2242
|
}
|
|
2243
|
+
/**
|
|
2244
|
+
* When a query invoke request is fulfilled, clean up and trigger the next queued
|
|
2245
|
+
* request if one exists.
|
|
2246
|
+
*/
|
|
2247
|
+
onInvokeQueryRequestFulfilled(queryName, variables, mapKey, requestId) {
|
|
2248
|
+
this.cleanupInvokeQueryRequest(requestId, mapKey);
|
|
2249
|
+
const deferredCancel = this.pendingCancellations.get(requestId);
|
|
2250
|
+
if (deferredCancel) {
|
|
2251
|
+
this.pendingCancellations.delete(requestId);
|
|
2252
|
+
this.cancelSubscription(requestId, mapKey);
|
|
2253
|
+
}
|
|
2254
|
+
const queuedRequestPromise = this.queuedInvokeQueryRequests.get(mapKey);
|
|
2255
|
+
if (!queuedRequestPromise) {
|
|
2256
|
+
if (!this.hasActiveSubscriptions && !this.hasActiveExecuteRequests) {
|
|
2257
|
+
this.startIdleCloseTimeout();
|
|
2258
|
+
}
|
|
2259
|
+
return;
|
|
2260
|
+
}
|
|
2261
|
+
this.queuedInvokeQueryRequests.delete(mapKey);
|
|
2262
|
+
void this.executeOrResumeQuery(queryName, variables, mapKey, queuedRequestPromise);
|
|
2263
|
+
}
|
|
2012
2264
|
/**
|
|
2013
2265
|
* @inheritdoc
|
|
2014
2266
|
* @remarks
|
|
@@ -2024,13 +2276,11 @@ class AbstractDataConnectStreamTransport extends AbstractDataConnectTransport {
|
|
|
2024
2276
|
requestId,
|
|
2025
2277
|
execute: activeRequestKey
|
|
2026
2278
|
};
|
|
2027
|
-
let { responsePromise, rejectFn } = this.
|
|
2279
|
+
let { responsePromise, rejectFn } = this.trackInvokeMutationRequest(requestId, mapKey, executeBody);
|
|
2028
2280
|
responsePromise = responsePromise.finally(() => {
|
|
2029
|
-
this.
|
|
2030
|
-
if (!this.hasActiveSubscriptions &&
|
|
2031
|
-
|
|
2032
|
-
this.closeTimeoutFinished) {
|
|
2033
|
-
void this.attemptClose();
|
|
2281
|
+
this.cleanupInvokeMutationRequest(requestId, mapKey);
|
|
2282
|
+
if (!this.hasActiveSubscriptions && !this.hasActiveExecuteRequests) {
|
|
2283
|
+
this.startIdleCloseTimeout();
|
|
2034
2284
|
}
|
|
2035
2285
|
});
|
|
2036
2286
|
// asynchronous, fire and forget
|
|
@@ -2048,24 +2298,35 @@ class AbstractDataConnectStreamTransport extends AbstractDataConnectTransport {
|
|
|
2048
2298
|
* before the method returns.
|
|
2049
2299
|
*/
|
|
2050
2300
|
invokeSubscribe(observer, queryName, variables) {
|
|
2051
|
-
// if we are waiting to close the stream, cancel closing!
|
|
2052
|
-
this.cancelClose();
|
|
2053
|
-
const requestId = this.nextRequestId();
|
|
2054
|
-
const activeRequestKey = { operationName: queryName, variables };
|
|
2055
2301
|
const mapKey = this.getMapKey(queryName, variables);
|
|
2056
|
-
const
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
observer.onError(err instanceof Error ? err : new Error(String(err)));
|
|
2064
|
-
this.cleanupSubscribeRequest(requestId, mapKey);
|
|
2065
|
-
if (!this.hasActiveSubscriptions) {
|
|
2066
|
-
this.prepareToCloseGracefully();
|
|
2302
|
+
const existingSubscribe = this.activeInvokeSubscribeRequests.get(mapKey);
|
|
2303
|
+
// if this query is pending cancellation, cancel the cancellation!
|
|
2304
|
+
if (existingSubscribe) {
|
|
2305
|
+
const requestId = existingSubscribe.requestId;
|
|
2306
|
+
if (this.pendingCancellations.has(requestId)) {
|
|
2307
|
+
this.pendingCancellations.delete(requestId);
|
|
2308
|
+
this.subscribeObservers.set(requestId, observer);
|
|
2067
2309
|
}
|
|
2068
|
-
}
|
|
2310
|
+
}
|
|
2311
|
+
else {
|
|
2312
|
+
const requestId = this.nextRequestId();
|
|
2313
|
+
const activeRequestKey = { operationName: queryName, variables };
|
|
2314
|
+
const subscribeBody = {
|
|
2315
|
+
requestId,
|
|
2316
|
+
subscribe: activeRequestKey
|
|
2317
|
+
};
|
|
2318
|
+
this.trackInvokeSubscribeRequest(requestId, mapKey, subscribeBody, observer);
|
|
2319
|
+
// asynchronous, fire and forget
|
|
2320
|
+
this.sendRequestMessage(subscribeBody).catch(err => {
|
|
2321
|
+
observer.onError(err instanceof Error ? err : new Error(String(err)));
|
|
2322
|
+
this.cleanupInvokeSubscribeRequest(requestId, mapKey);
|
|
2323
|
+
if (!this.hasActiveSubscriptions) {
|
|
2324
|
+
this.startIdleCloseTimeout();
|
|
2325
|
+
}
|
|
2326
|
+
});
|
|
2327
|
+
}
|
|
2328
|
+
// if we are waiting to close the stream, cancel closing!
|
|
2329
|
+
this.cancelClose();
|
|
2069
2330
|
}
|
|
2070
2331
|
/**
|
|
2071
2332
|
* @inheritdoc
|
|
@@ -2076,22 +2337,38 @@ class AbstractDataConnectStreamTransport extends AbstractDataConnectTransport {
|
|
|
2076
2337
|
*/
|
|
2077
2338
|
invokeUnsubscribe(queryName, variables) {
|
|
2078
2339
|
const mapKey = this.getMapKey(queryName, variables);
|
|
2079
|
-
const subscribeRequest = this.
|
|
2340
|
+
const subscribeRequest = this.activeInvokeSubscribeRequests.get(mapKey);
|
|
2080
2341
|
if (!subscribeRequest) {
|
|
2081
2342
|
return;
|
|
2082
2343
|
}
|
|
2083
2344
|
const requestId = subscribeRequest.requestId;
|
|
2345
|
+
this.subscribeObservers.delete(requestId);
|
|
2346
|
+
const resumePromise = this.resumeRequestPromises.get(requestId);
|
|
2347
|
+
if (resumePromise) {
|
|
2348
|
+
this.pendingCancellations.set(requestId, {
|
|
2349
|
+
operationName: queryName,
|
|
2350
|
+
variables
|
|
2351
|
+
});
|
|
2352
|
+
return;
|
|
2353
|
+
}
|
|
2354
|
+
this.cancelSubscription(requestId, mapKey);
|
|
2355
|
+
}
|
|
2356
|
+
/**
|
|
2357
|
+
* Cancels a subscription, cleans up the request tracking data structures, and checks to see if we
|
|
2358
|
+
* should close the stream due to inactivity.
|
|
2359
|
+
*/
|
|
2360
|
+
cancelSubscription(requestId, mapKey) {
|
|
2361
|
+
this.cleanupInvokeSubscribeRequest(requestId, mapKey);
|
|
2084
2362
|
const cancelBody = {
|
|
2085
2363
|
requestId,
|
|
2086
2364
|
cancel: {}
|
|
2087
2365
|
};
|
|
2088
|
-
this.cleanupSubscribeRequest(requestId, mapKey);
|
|
2089
2366
|
// asynchronous, fire and forget
|
|
2090
2367
|
this.sendRequestMessage(cancelBody).catch(err => {
|
|
2091
2368
|
logError(`Stream Transport failed to send unsubscribe message: ${err}`);
|
|
2092
2369
|
});
|
|
2093
2370
|
if (!this.hasActiveSubscriptions) {
|
|
2094
|
-
this.
|
|
2371
|
+
this.startIdleCloseTimeout();
|
|
2095
2372
|
}
|
|
2096
2373
|
}
|
|
2097
2374
|
onAuthTokenChanged(newToken) {
|
|
@@ -2110,38 +2387,58 @@ class AbstractDataConnectStreamTransport extends AbstractDataConnectTransport {
|
|
|
2110
2387
|
(!oldAuthUid && newAuthUid) || // user logged in
|
|
2111
2388
|
(oldAuthUid && newAuthUid !== oldAuthUid) // logged in user changed
|
|
2112
2389
|
) {
|
|
2113
|
-
this.
|
|
2114
|
-
void this.attemptClose();
|
|
2390
|
+
void this.cleanupAndTerminate(Code.UNAUTHORIZED, 'Stream disconnected due to auth change.');
|
|
2115
2391
|
}
|
|
2116
2392
|
}
|
|
2117
2393
|
/**
|
|
2118
2394
|
* Handle a response message from the server. Called by the connection-specific implementation after
|
|
2119
|
-
* it's transformed a message from the server into a {@
|
|
2120
|
-
* @param requestId the
|
|
2395
|
+
* it's transformed a message from the server into a {@linkcode DataConnectResponse}.
|
|
2396
|
+
* @param requestId the Request ID associated with this response.
|
|
2121
2397
|
* @param response the response from the server.
|
|
2122
2398
|
*/
|
|
2123
2399
|
async handleResponse(requestId, response) {
|
|
2124
2400
|
if (this.executeRequestPromises.has(requestId)) {
|
|
2125
|
-
// don't clean up the tracking maps here, they're handled automatically when the execute promise settles
|
|
2126
2401
|
const { resolveFn, rejectFn } = this.executeRequestPromises.get(requestId);
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2402
|
+
this.handleInvokeOperationResponse(resolveFn, rejectFn, response);
|
|
2403
|
+
}
|
|
2404
|
+
else if (this.subscribeObservers.has(requestId) ||
|
|
2405
|
+
this.resumeRequestPromises.has(requestId)) {
|
|
2406
|
+
const observer = this.subscribeObservers.get(requestId);
|
|
2407
|
+
const resumePromise = this.resumeRequestPromises.get(requestId);
|
|
2408
|
+
if (resumePromise) {
|
|
2409
|
+
this.resumeRequestPromises.delete(requestId);
|
|
2410
|
+
const { resolveFn, rejectFn } = resumePromise;
|
|
2411
|
+
this.handleInvokeOperationResponse(resolveFn, rejectFn, response);
|
|
2134
2412
|
}
|
|
2135
|
-
|
|
2136
|
-
|
|
2413
|
+
if (observer) {
|
|
2414
|
+
try {
|
|
2415
|
+
await observer.onData(response);
|
|
2416
|
+
}
|
|
2417
|
+
catch (e) {
|
|
2418
|
+
logError(`Error in observer callback: ${e}`);
|
|
2419
|
+
}
|
|
2137
2420
|
}
|
|
2138
2421
|
}
|
|
2139
|
-
else
|
|
2140
|
-
|
|
2141
|
-
|
|
2422
|
+
else {
|
|
2423
|
+
logError(`Stream response contained unrecognized requestId '${requestId}'`);
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
/**
|
|
2427
|
+
* Handles an invoke operation response, resolving or rejecting the promise returned to the user
|
|
2428
|
+
* Does not handle any cleanup for requests - this should be handled by the caller or the promise's
|
|
2429
|
+
* finally() block.
|
|
2430
|
+
*/
|
|
2431
|
+
handleInvokeOperationResponse(resolveFn, rejectFn, response) {
|
|
2432
|
+
if (response.errors && response.errors.length) {
|
|
2433
|
+
const failureResponse = {
|
|
2434
|
+
errors: response.errors,
|
|
2435
|
+
data: response.data
|
|
2436
|
+
};
|
|
2437
|
+
const stringified = JSON.stringify(response.errors);
|
|
2438
|
+
rejectFn(new DataConnectOperationError('DataConnect error while performing request: ' + stringified, failureResponse));
|
|
2142
2439
|
}
|
|
2143
2440
|
else {
|
|
2144
|
-
|
|
2441
|
+
resolveFn(response);
|
|
2145
2442
|
}
|
|
2146
2443
|
}
|
|
2147
2444
|
}
|
|
@@ -2177,6 +2474,18 @@ const WEBSOCKET_CLOSE_CODE = 1000;
|
|
|
2177
2474
|
* @internal
|
|
2178
2475
|
*/
|
|
2179
2476
|
class WebSocketTransport extends AbstractDataConnectStreamTransport {
|
|
2477
|
+
constructor() {
|
|
2478
|
+
super(...arguments);
|
|
2479
|
+
/** Decodes binary WebSocket responses to strings */
|
|
2480
|
+
this.decoder = undefined;
|
|
2481
|
+
/** The current connection to the server. Undefined if disconnected. */
|
|
2482
|
+
this.connection = undefined;
|
|
2483
|
+
/**
|
|
2484
|
+
* Current connection attempt. If null, we are not currently attemping to connect (not connected,
|
|
2485
|
+
* or already connected). Will be resolved or rejected when the connection is opened or fails to open.
|
|
2486
|
+
*/
|
|
2487
|
+
this.connectionAttempt = null;
|
|
2488
|
+
}
|
|
2180
2489
|
get endpointUrl() {
|
|
2181
2490
|
return websocketUrlBuilder({
|
|
2182
2491
|
connector: this._connectorName,
|
|
@@ -2202,24 +2511,6 @@ class WebSocketTransport extends AbstractDataConnectStreamTransport {
|
|
|
2202
2511
|
get streamIsReady() {
|
|
2203
2512
|
return this.connection?.readyState === WebSocket.OPEN;
|
|
2204
2513
|
}
|
|
2205
|
-
constructor(options, apiKey, appId, authProvider, appCheckProvider, transportOptions, _isUsingGen = false, _callerSdkType = CallerSdkTypeEnum.Base) {
|
|
2206
|
-
super(options, apiKey, appId, authProvider, appCheckProvider, transportOptions, _isUsingGen, _callerSdkType);
|
|
2207
|
-
this.apiKey = apiKey;
|
|
2208
|
-
this.appId = appId;
|
|
2209
|
-
this.authProvider = authProvider;
|
|
2210
|
-
this.appCheckProvider = appCheckProvider;
|
|
2211
|
-
this._isUsingGen = _isUsingGen;
|
|
2212
|
-
this._callerSdkType = _callerSdkType;
|
|
2213
|
-
/** Decodes binary WebSocket responses to strings */
|
|
2214
|
-
this.decoder = undefined;
|
|
2215
|
-
/** The current connection to the server. Undefined if disconnected. */
|
|
2216
|
-
this.connection = undefined;
|
|
2217
|
-
/**
|
|
2218
|
-
* Current connection attempt. If null, we are not currently attemping to connect (not connected,
|
|
2219
|
-
* or already connected). Will be resolved or rejected when the connection is opened or fails to open.
|
|
2220
|
-
*/
|
|
2221
|
-
this.connectionAttempt = null;
|
|
2222
|
-
}
|
|
2223
2514
|
ensureConnection() {
|
|
2224
2515
|
try {
|
|
2225
2516
|
if (this.streamIsReady) {
|
|
@@ -2439,7 +2730,7 @@ class DataConnectTransportManager {
|
|
|
2439
2730
|
if (this.isUsingEmulator && this.transportOptions) {
|
|
2440
2731
|
this.streamTransport.useEmulator(this.transportOptions.host, this.transportOptions.port, this.transportOptions.sslEnabled);
|
|
2441
2732
|
}
|
|
2442
|
-
this.streamTransport.
|
|
2733
|
+
this.streamTransport.onCloseCallback = () => {
|
|
2443
2734
|
this.streamTransport = undefined;
|
|
2444
2735
|
};
|
|
2445
2736
|
}
|