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