@terreno/rtk 0.13.0 → 0.14.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/authSlice.test.js +2 -10
- package/dist/authSlice.test.js.map +1 -1
- package/dist/authSliceNative.test.js +11 -3
- package/dist/authSliceNative.test.js.map +1 -1
- package/dist/betterAuthSlice.test.js +1 -25
- package/dist/betterAuthSlice.test.js.map +1 -1
- package/dist/buildNumber.test.js +19 -0
- package/dist/buildNumber.test.js.map +1 -1
- package/dist/constants.d.ts +3 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +11 -3
- package/dist/constants.js.map +1 -1
- package/dist/constants.test.js +44 -2
- package/dist/constants.test.js.map +1 -1
- package/dist/emptyApi.d.ts +2 -0
- package/dist/emptyApi.d.ts.map +1 -1
- package/dist/emptyApi.js +5 -0
- package/dist/emptyApi.js.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/isolated/constants.isolated.js +7 -1
- package/dist/isolated/constants.isolated.js.map +1 -1
- package/dist/isolated/offlineMiddleware.isolated.d.ts +2 -0
- package/dist/isolated/offlineMiddleware.isolated.d.ts.map +1 -0
- package/dist/isolated/offlineMiddleware.isolated.js +516 -0
- package/dist/isolated/offlineMiddleware.isolated.js.map +1 -0
- package/dist/isolated/offlineMiddleware.native.isolated.d.ts +2 -0
- package/dist/isolated/offlineMiddleware.native.isolated.d.ts.map +1 -0
- package/dist/isolated/offlineMiddleware.native.isolated.js +70 -0
- package/dist/isolated/offlineMiddleware.native.isolated.js.map +1 -0
- package/dist/isolated/useUpgradeCheck.mobile.isolated.d.ts +2 -0
- package/dist/isolated/useUpgradeCheck.mobile.isolated.d.ts.map +1 -0
- package/dist/{useUpgradeCheck.mobile.test.js → isolated/useUpgradeCheck.mobile.isolated.js} +13 -5
- package/dist/isolated/useUpgradeCheck.mobile.isolated.js.map +1 -0
- package/dist/isolated/useUpgradeCheck.web.isolated.d.ts +2 -0
- package/dist/isolated/useUpgradeCheck.web.isolated.d.ts.map +1 -0
- package/dist/{useUpgradeCheck.test.js → isolated/useUpgradeCheck.web.isolated.js} +37 -7
- package/dist/isolated/useUpgradeCheck.web.isolated.js.map +1 -0
- package/dist/mongooseSlice.test.js +1 -1
- package/dist/mongooseSlice.test.js.map +1 -1
- package/dist/offlineGate.d.ts +8 -0
- package/dist/offlineGate.d.ts.map +1 -0
- package/dist/offlineGate.js +20 -0
- package/dist/offlineGate.js.map +1 -0
- package/dist/offlineGate.test.d.ts +2 -0
- package/dist/offlineGate.test.d.ts.map +1 -0
- package/dist/offlineGate.test.js +46 -0
- package/dist/offlineGate.test.js.map +1 -0
- package/dist/offlineMiddleware.d.ts +46 -0
- package/dist/offlineMiddleware.d.ts.map +1 -0
- package/dist/offlineMiddleware.js +591 -0
- package/dist/offlineMiddleware.js.map +1 -0
- package/dist/offlineMiddleware.test.d.ts +2 -0
- package/dist/offlineMiddleware.test.d.ts.map +1 -0
- package/dist/offlineMiddleware.test.js +145 -0
- package/dist/offlineMiddleware.test.js.map +1 -0
- package/dist/offlineSlice.d.ts +232 -0
- package/dist/offlineSlice.d.ts.map +1 -0
- package/dist/offlineSlice.js +65 -0
- package/dist/offlineSlice.js.map +1 -0
- package/dist/offlineSlice.test.d.ts +2 -0
- package/dist/offlineSlice.test.d.ts.map +1 -0
- package/dist/offlineSlice.test.js +153 -0
- package/dist/offlineSlice.test.js.map +1 -0
- package/dist/realtime.d.ts +123 -0
- package/dist/realtime.d.ts.map +1 -0
- package/dist/realtime.js +299 -0
- package/dist/realtime.js.map +1 -0
- package/dist/realtime.test.d.ts +2 -0
- package/dist/realtime.test.d.ts.map +1 -0
- package/dist/realtime.test.js +339 -0
- package/dist/realtime.test.js.map +1 -0
- package/dist/socket.d.ts.map +1 -1
- package/dist/socket.js +29 -28
- package/dist/socket.js.map +1 -1
- package/dist/sync.d.ts +35 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +205 -0
- package/dist/sync.js.map +1 -0
- package/dist/tagGenerator.d.ts +20 -1
- package/dist/tagGenerator.d.ts.map +1 -1
- package/dist/tagGenerator.js +1 -2
- package/dist/tagGenerator.js.map +1 -1
- package/dist/useOfflineStatus.d.ts +33 -0
- package/dist/useOfflineStatus.d.ts.map +1 -0
- package/dist/useOfflineStatus.js +43 -0
- package/dist/useOfflineStatus.js.map +1 -0
- package/dist/useOfflineStatus.test.d.ts +2 -0
- package/dist/useOfflineStatus.test.d.ts.map +1 -0
- package/dist/useOfflineStatus.test.js +119 -0
- package/dist/useOfflineStatus.test.js.map +1 -0
- package/dist/useRealtimeDebug.d.ts +6 -0
- package/dist/useRealtimeDebug.d.ts.map +1 -0
- package/dist/useRealtimeDebug.js +41 -0
- package/dist/useRealtimeDebug.js.map +1 -0
- package/dist/useServerStatus.d.ts +29 -0
- package/dist/useServerStatus.d.ts.map +1 -0
- package/dist/useServerStatus.js +89 -0
- package/dist/useServerStatus.js.map +1 -0
- package/dist/useUpgradeCheck.d.ts +7 -3
- package/dist/useUpgradeCheck.d.ts.map +1 -1
- package/dist/useUpgradeCheck.js +27 -13
- package/dist/useUpgradeCheck.js.map +1 -1
- package/package.json +2 -2
- package/src/authSlice.test.ts +2 -11
- package/src/authSliceNative.test.ts +11 -4
- package/src/betterAuthSlice.test.ts +1 -25
- package/src/buildNumber.test.ts +19 -0
- package/src/constants.test.ts +54 -1
- package/src/constants.ts +14 -3
- package/src/emptyApi.ts +8 -0
- package/src/index.ts +8 -0
- package/src/isolated/constants.isolated.ts +7 -3
- package/src/isolated/offlineMiddleware.isolated.ts +696 -0
- package/src/isolated/offlineMiddleware.native.isolated.ts +80 -0
- package/src/{useUpgradeCheck.mobile.test.ts → isolated/useUpgradeCheck.mobile.isolated.ts} +13 -4
- package/src/{useUpgradeCheck.test.ts → isolated/useUpgradeCheck.web.isolated.ts} +48 -6
- package/src/mongooseSlice.test.ts +1 -1
- package/src/offlineGate.test.ts +55 -0
- package/src/offlineGate.ts +25 -0
- package/src/offlineMiddleware.test.ts +187 -0
- package/src/offlineMiddleware.ts +718 -0
- package/src/offlineSlice.test.ts +204 -0
- package/src/offlineSlice.ts +130 -0
- package/src/realtime.test.ts +404 -0
- package/src/realtime.ts +408 -0
- package/src/socket.ts +33 -32
- package/src/sync.ts +310 -0
- package/src/tagGenerator.ts +29 -11
- package/src/useOfflineStatus.test.ts +157 -0
- package/src/useOfflineStatus.ts +82 -0
- package/src/useRealtimeDebug.ts +51 -0
- package/src/useServerStatus.ts +122 -0
- package/src/useUpgradeCheck.ts +35 -13
- package/dist/isolated/useUpgradeCheck.isolated.d.ts +0 -2
- package/dist/isolated/useUpgradeCheck.isolated.d.ts.map +0 -1
- package/dist/isolated/useUpgradeCheck.isolated.js +0 -153
- package/dist/isolated/useUpgradeCheck.isolated.js.map +0 -1
- package/dist/useUpgradeCheck.mobile.test.d.ts +0 -2
- package/dist/useUpgradeCheck.mobile.test.d.ts.map +0 -1
- package/dist/useUpgradeCheck.mobile.test.js.map +0 -1
- package/dist/useUpgradeCheck.test.d.ts +0 -2
- package/dist/useUpgradeCheck.test.d.ts.map +0 -1
- package/dist/useUpgradeCheck.test.js.map +0 -1
- package/src/isolated/useUpgradeCheck.isolated.ts +0 -201
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
2
|
+
import { useDispatch } from "react-redux";
|
|
3
|
+
import { baseUrl } from "./constants";
|
|
4
|
+
import { setOnlineStatus } from "./offlineSlice";
|
|
5
|
+
import { useOfflineStatus } from "./useOfflineStatus";
|
|
6
|
+
const DEFAULT_POLL_INTERVAL_MS = 5000;
|
|
7
|
+
const OFFLINE_POLL_INTERVAL_MS = 3000;
|
|
8
|
+
/**
|
|
9
|
+
* Polls the API server health endpoint to determine actual server reachability.
|
|
10
|
+
* Dispatches setOnlineStatus(true/false) into the offline slice so the rest
|
|
11
|
+
* of the offline middleware (queue, optimistic updates, replay) reacts.
|
|
12
|
+
*
|
|
13
|
+
* Use this instead of useOfflineStatus when you want real server-connectivity
|
|
14
|
+
* detection rather than just browser navigator.onLine.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const {isOnline, queueLength, isSyncing, isLocalOnly} = useServerStatus({
|
|
19
|
+
* skip: !userId,
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export const useServerStatus = (options = {}) => {
|
|
24
|
+
const { healthUrl = `${baseUrl}/health`, pollIntervalMs = DEFAULT_POLL_INTERVAL_MS, offlinePollIntervalMs = OFFLINE_POLL_INTERVAL_MS, skip = false, } = options;
|
|
25
|
+
const offlineStatus = useOfflineStatus();
|
|
26
|
+
const dispatch = useDispatch();
|
|
27
|
+
const isOnlineRef = useRef(offlineStatus.isOnline);
|
|
28
|
+
isOnlineRef.current = offlineStatus.isOnline;
|
|
29
|
+
// Ping the server health endpoint; dispatch status changes
|
|
30
|
+
const checkHealth = useCallback(async () => {
|
|
31
|
+
try {
|
|
32
|
+
const controller = new AbortController();
|
|
33
|
+
const timeout = setTimeout(() => controller.abort(), 4000);
|
|
34
|
+
const response = await fetch(healthUrl, {
|
|
35
|
+
cache: "no-store",
|
|
36
|
+
method: "GET",
|
|
37
|
+
signal: controller.signal,
|
|
38
|
+
});
|
|
39
|
+
clearTimeout(timeout);
|
|
40
|
+
if (response.ok && !isOnlineRef.current) {
|
|
41
|
+
dispatch(setOnlineStatus(true));
|
|
42
|
+
}
|
|
43
|
+
else if (!response.ok && isOnlineRef.current) {
|
|
44
|
+
dispatch(setOnlineStatus(false));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
if (isOnlineRef.current) {
|
|
49
|
+
dispatch(setOnlineStatus(false));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}, [healthUrl, dispatch]);
|
|
53
|
+
// Poll at different intervals depending on online/offline state
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (skip || typeof window === "undefined") {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
// Check immediately on mount
|
|
59
|
+
void checkHealth();
|
|
60
|
+
const interval = offlineStatus.isOnline ? pollIntervalMs : offlinePollIntervalMs;
|
|
61
|
+
const id = setInterval(() => {
|
|
62
|
+
void checkHealth();
|
|
63
|
+
}, interval);
|
|
64
|
+
return () => {
|
|
65
|
+
clearInterval(id);
|
|
66
|
+
};
|
|
67
|
+
}, [skip, checkHealth, offlineStatus.isOnline, pollIntervalMs, offlinePollIntervalMs]);
|
|
68
|
+
// Also respond to browser online/offline events for instant detection
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (skip || typeof window === "undefined") {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const handleOffline = () => {
|
|
74
|
+
dispatch(setOnlineStatus(false));
|
|
75
|
+
};
|
|
76
|
+
const handleOnline = () => {
|
|
77
|
+
// Don't trust the browser event alone — verify with health check
|
|
78
|
+
void checkHealth();
|
|
79
|
+
};
|
|
80
|
+
window.addEventListener("online", handleOnline);
|
|
81
|
+
window.addEventListener("offline", handleOffline);
|
|
82
|
+
return () => {
|
|
83
|
+
window.removeEventListener("online", handleOnline);
|
|
84
|
+
window.removeEventListener("offline", handleOffline);
|
|
85
|
+
};
|
|
86
|
+
}, [skip, dispatch, checkHealth]);
|
|
87
|
+
return offlineStatus;
|
|
88
|
+
};
|
|
89
|
+
//# sourceMappingURL=useServerStatus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useServerStatus.js","sourceRoot":"","sources":["../src/useServerStatus.ts"],"names":[],"mappings":"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"}
|
|
@@ -29,10 +29,14 @@ interface UseUpgradeCheckResult {
|
|
|
29
29
|
* - `isWarning` — the build is below the warning threshold; the caller
|
|
30
30
|
* can render a dismissible `Banner` or similar prompt.
|
|
31
31
|
*
|
|
32
|
-
*
|
|
33
|
-
* `
|
|
32
|
+
* The polling interval is server-driven: the first successful `/version-check`
|
|
33
|
+
* response returns `pollingIntervalMs` from the backend's VersionConfig and the
|
|
34
|
+
* hook uses that value for all subsequent intervals. Pass `pollingIntervalMs` in
|
|
35
|
+
* options as a local fallback that is active until the first server response
|
|
36
|
+
* arrives. Pass `recheckOnForeground` to also re-check when the app/tab
|
|
37
|
+
* returns to the foreground.
|
|
34
38
|
*
|
|
35
|
-
* @param options - Optional polling and foreground re-check configuration.
|
|
39
|
+
* @param options - Optional fallback polling interval and foreground re-check configuration.
|
|
36
40
|
* @returns Current upgrade status, messages, and an `onUpdate` callback.
|
|
37
41
|
*/
|
|
38
42
|
export declare const useUpgradeCheck: (options?: UseUpgradeCheckOptions) => UseUpgradeCheckResult;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useUpgradeCheck.d.ts","sourceRoot":"","sources":["../src/useUpgradeCheck.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useUpgradeCheck.d.ts","sourceRoot":"","sources":["../src/useUpgradeCheck.ts"],"names":[],"mappings":"AAWA,UAAU,sBAAsB;IAC9B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,UAAU,qBAAqB;IAC7B,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oGAAoG;IACpG,iBAAiB,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,eAAe,GAAI,UAAU,sBAAsB,KAAG,qBAwHlE,CAAC"}
|
package/dist/useUpgradeCheck.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import Constants from "expo-constants";
|
|
2
2
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
-
import
|
|
3
|
+
// Use a namespace import + lazy property access so per-test `mock.module` swaps
|
|
4
|
+
// reach the runtime values. A destructured `import {AppState, Linking}` fails
|
|
5
|
+
// bun's static-export check against react-native's CJS getter exports under the
|
|
6
|
+
// version pinned in CI.
|
|
7
|
+
import * as ReactNative from "react-native";
|
|
4
8
|
import { useLazyGetVersionCheckQuery } from "./emptyApi";
|
|
5
9
|
import { IsWeb } from "./platform";
|
|
6
10
|
/**
|
|
@@ -12,23 +16,29 @@ import { IsWeb } from "./platform";
|
|
|
12
16
|
* - `isWarning` — the build is below the warning threshold; the caller
|
|
13
17
|
* can render a dismissible `Banner` or similar prompt.
|
|
14
18
|
*
|
|
15
|
-
*
|
|
16
|
-
* `
|
|
19
|
+
* The polling interval is server-driven: the first successful `/version-check`
|
|
20
|
+
* response returns `pollingIntervalMs` from the backend's VersionConfig and the
|
|
21
|
+
* hook uses that value for all subsequent intervals. Pass `pollingIntervalMs` in
|
|
22
|
+
* options as a local fallback that is active until the first server response
|
|
23
|
+
* arrives. Pass `recheckOnForeground` to also re-check when the app/tab
|
|
24
|
+
* returns to the foreground.
|
|
17
25
|
*
|
|
18
|
-
* @param options - Optional polling and foreground re-check configuration.
|
|
26
|
+
* @param options - Optional fallback polling interval and foreground re-check configuration.
|
|
19
27
|
* @returns Current upgrade status, messages, and an `onUpdate` callback.
|
|
20
28
|
*/
|
|
21
29
|
export const useUpgradeCheck = (options) => {
|
|
22
|
-
const { pollingIntervalMs, recheckOnForeground = false } = options ?? {};
|
|
30
|
+
const { pollingIntervalMs: fallbackPollingIntervalMs, recheckOnForeground = false } = options ?? {};
|
|
23
31
|
const [isRequired, setIsRequired] = useState(false);
|
|
24
32
|
const [isWarning, setIsWarning] = useState(false);
|
|
25
33
|
const [requiredMessage, setRequiredMessage] = useState();
|
|
26
34
|
const [warningMessage, setWarningMessage] = useState();
|
|
27
35
|
const [updateUrl, setUpdateUrl] = useState();
|
|
28
36
|
const [warningCheckCount, setWarningCheckCount] = useState(0);
|
|
37
|
+
// Starts with the local fallback; updated to the server-configured value after the first response.
|
|
38
|
+
const [activePollingIntervalMs, setActivePollingIntervalMs] = useState(fallbackPollingIntervalMs);
|
|
29
39
|
const [triggerVersionCheck] = useLazyGetVersionCheckQuery();
|
|
30
40
|
const buildNumber = Constants.expoConfig?.extra?.buildNumber;
|
|
31
|
-
const appState = useRef(AppState.currentState);
|
|
41
|
+
const appState = useRef(ReactNative.AppState.currentState);
|
|
32
42
|
// Process version-check response inline via .unwrap() so every poll trigger
|
|
33
43
|
// is handled, even when RTK Query returns a structurally-shared cached response
|
|
34
44
|
// (which would prevent a useEffect from re-firing).
|
|
@@ -40,7 +50,7 @@ export const useUpgradeCheck = (options) => {
|
|
|
40
50
|
triggerVersionCheck({ platform, version: buildNumber })
|
|
41
51
|
.unwrap()
|
|
42
52
|
.then((data) => {
|
|
43
|
-
const { message, status, updateUrl: responseUpdateUrl } = data;
|
|
53
|
+
const { message, pollingIntervalMs: serverPollingIntervalMs, status, updateUrl: responseUpdateUrl, } = data;
|
|
44
54
|
if (status === "required") {
|
|
45
55
|
setIsRequired(true);
|
|
46
56
|
setRequiredMessage(message);
|
|
@@ -58,6 +68,10 @@ export const useUpgradeCheck = (options) => {
|
|
|
58
68
|
if (responseUpdateUrl) {
|
|
59
69
|
setUpdateUrl(responseUpdateUrl);
|
|
60
70
|
}
|
|
71
|
+
// Adopt the server-configured polling interval once it's available.
|
|
72
|
+
if (serverPollingIntervalMs !== undefined) {
|
|
73
|
+
setActivePollingIntervalMs(serverPollingIntervalMs);
|
|
74
|
+
}
|
|
61
75
|
})
|
|
62
76
|
.catch((error) => {
|
|
63
77
|
console.debug("Version check failed, continuing normally", error);
|
|
@@ -69,7 +83,7 @@ export const useUpgradeCheck = (options) => {
|
|
|
69
83
|
return;
|
|
70
84
|
}
|
|
71
85
|
if (updateUrl) {
|
|
72
|
-
void Linking.openURL(updateUrl).catch((err) => {
|
|
86
|
+
void ReactNative.Linking.openURL(updateUrl).catch((err) => {
|
|
73
87
|
console.warn("Failed to open update URL", err);
|
|
74
88
|
});
|
|
75
89
|
}
|
|
@@ -81,20 +95,20 @@ export const useUpgradeCheck = (options) => {
|
|
|
81
95
|
useEffect(() => {
|
|
82
96
|
runCheck();
|
|
83
97
|
}, [runCheck]);
|
|
84
|
-
// Periodic re-check
|
|
98
|
+
// Periodic re-check using the server-driven interval (falls back to the local prop until first response).
|
|
85
99
|
useEffect(() => {
|
|
86
|
-
if (!
|
|
100
|
+
if (!activePollingIntervalMs) {
|
|
87
101
|
return;
|
|
88
102
|
}
|
|
89
|
-
const interval = setInterval(runCheck,
|
|
103
|
+
const interval = setInterval(runCheck, activePollingIntervalMs);
|
|
90
104
|
return () => clearInterval(interval);
|
|
91
|
-
}, [runCheck,
|
|
105
|
+
}, [runCheck, activePollingIntervalMs]);
|
|
92
106
|
// Re-check when app/tab returns to foreground
|
|
93
107
|
useEffect(() => {
|
|
94
108
|
if (!recheckOnForeground) {
|
|
95
109
|
return;
|
|
96
110
|
}
|
|
97
|
-
const subscription = AppState.addEventListener("change", (nextAppState) => {
|
|
111
|
+
const subscription = ReactNative.AppState.addEventListener("change", (nextAppState) => {
|
|
98
112
|
const wasBackground = /inactive|background/.test(appState.current);
|
|
99
113
|
const isNowActive = nextAppState === "active";
|
|
100
114
|
if (wasBackground && isNowActive) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useUpgradeCheck.js","sourceRoot":"","sources":["../src/useUpgradeCheck.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,gBAAgB,CAAC;AACvC,OAAO,EAAC,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAC,MAAM,OAAO,CAAC;AAC/D,
|
|
1
|
+
{"version":3,"file":"useUpgradeCheck.js","sourceRoot":"","sources":["../src/useUpgradeCheck.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"@better-auth/expo": "^1.2.8",
|
|
4
4
|
"@react-native-async-storage/async-storage": "2.2.0",
|
|
5
5
|
"@reduxjs/toolkit": "^2.11.1",
|
|
6
|
-
"@terreno/ui": "0.
|
|
6
|
+
"@terreno/ui": "0.14.0",
|
|
7
7
|
"async-mutex": "^0.5.0",
|
|
8
8
|
"axios": "^1.13.2",
|
|
9
9
|
"axios-retry": "^4.5.0",
|
|
@@ -67,5 +67,5 @@
|
|
|
67
67
|
"test:coverage": "bun run ../scripts/check-coverage.ts"
|
|
68
68
|
},
|
|
69
69
|
"types": "dist/index.d.ts",
|
|
70
|
-
"version": "0.
|
|
70
|
+
"version": "0.14.0"
|
|
71
71
|
}
|
package/src/authSlice.test.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// biome-ignore-all lint/suspicious/noExplicitAny: test mock typing
|
|
1
2
|
import {beforeEach, describe, expect, it} from "bun:test";
|
|
2
3
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
3
4
|
import {configureStore} from "@reduxjs/toolkit";
|
|
@@ -40,10 +41,7 @@ const api = createApi({
|
|
|
40
41
|
});
|
|
41
42
|
|
|
42
43
|
const createTestStore = () => {
|
|
43
|
-
const {authReducer, middleware, ...rest} = generateAuthSlice(
|
|
44
|
-
// biome-ignore lint/suspicious/noExplicitAny: Test mock
|
|
45
|
-
api as any
|
|
46
|
-
);
|
|
44
|
+
const {authReducer, middleware, ...rest} = generateAuthSlice(api as any);
|
|
47
45
|
|
|
48
46
|
return {
|
|
49
47
|
...rest,
|
|
@@ -283,29 +281,24 @@ describe("generateAuthSlice", () => {
|
|
|
283
281
|
|
|
284
282
|
describe("selectors", () => {
|
|
285
283
|
it("selectCurrentUserId returns userId", () => {
|
|
286
|
-
// biome-ignore lint/suspicious/noExplicitAny: Test mock state
|
|
287
284
|
const state = {auth: {userId: "user-123"}} as any;
|
|
288
285
|
expect(selectCurrentUserId(state)).toBe("user-123");
|
|
289
286
|
});
|
|
290
287
|
|
|
291
288
|
it("selectCurrentUserId returns undefined when no auth state", () => {
|
|
292
|
-
// biome-ignore lint/suspicious/noExplicitAny: Test mock state
|
|
293
289
|
expect(selectCurrentUserId({} as any)).toBeUndefined();
|
|
294
290
|
});
|
|
295
291
|
|
|
296
292
|
it("selectIsAuthenticating returns isAuthenticating", () => {
|
|
297
|
-
// biome-ignore lint/suspicious/noExplicitAny: Test mock state
|
|
298
293
|
const state = {auth: {isAuthenticating: true}} as any;
|
|
299
294
|
expect(selectIsAuthenticating(state)).toBe(true);
|
|
300
295
|
});
|
|
301
296
|
|
|
302
297
|
it("selectIsAuthenticating defaults to false", () => {
|
|
303
|
-
// biome-ignore lint/suspicious/noExplicitAny: Test mock state
|
|
304
298
|
expect(selectIsAuthenticating({} as any)).toBe(false);
|
|
305
299
|
});
|
|
306
300
|
|
|
307
301
|
it("selectLastTokenRefreshTimestamp returns timestamp", () => {
|
|
308
|
-
// biome-ignore lint/suspicious/noExplicitAny: Test mock state
|
|
309
302
|
const state = {auth: {lastTokenRefreshTimestamp: 12345}} as any;
|
|
310
303
|
expect(selectLastTokenRefreshTimestamp(state)).toBe(12345);
|
|
311
304
|
});
|
|
@@ -328,10 +321,8 @@ describe("EmailLoginRequest type", () => {
|
|
|
328
321
|
describe("generateProfileEndpoints", () => {
|
|
329
322
|
it("builds endpoint query payloads", () => {
|
|
330
323
|
const builder = {
|
|
331
|
-
// biome-ignore lint/suspicious/noExplicitAny: Testing generated endpoint configs
|
|
332
324
|
mutation: (config: any) => config,
|
|
333
325
|
};
|
|
334
|
-
// biome-ignore lint/suspicious/noExplicitAny: Lightweight fake builder for unit test
|
|
335
326
|
const endpoints = generateProfileEndpoints(builder as any, "todos");
|
|
336
327
|
const createEmailUserQuery = endpoints.createEmailUser.query;
|
|
337
328
|
const emailLoginQuery = endpoints.emailLogin.query;
|
|
@@ -1,6 +1,16 @@
|
|
|
1
|
+
// biome-ignore-all lint/suspicious/noExplicitAny: test mock typing
|
|
1
2
|
import {afterAll, afterEach, beforeEach, describe, expect, it, mock} from "bun:test";
|
|
2
3
|
|
|
4
|
+
// Keep this mock a superset of the preload's react-native mock so that other
|
|
5
|
+
// test files (e.g. useUpgradeCheck.test.ts) can still find AppState and Linking
|
|
6
|
+
// on the cached `react-native` module after authSliceNative.test.ts runs first.
|
|
7
|
+
// Bun caches the module by URL, so the last mock.module wins for subsequent loaders.
|
|
3
8
|
mock.module("react-native", () => ({
|
|
9
|
+
AppState: {
|
|
10
|
+
addEventListener: () => ({remove: () => {}}),
|
|
11
|
+
currentState: "active",
|
|
12
|
+
},
|
|
13
|
+
Linking: {openURL: async () => true},
|
|
4
14
|
Platform: {OS: "ios"},
|
|
5
15
|
StyleSheet: {create: (s: unknown) => s},
|
|
6
16
|
}));
|
|
@@ -59,10 +69,7 @@ const api = createApi({
|
|
|
59
69
|
});
|
|
60
70
|
|
|
61
71
|
const createTestStore = () => {
|
|
62
|
-
const {authReducer, middleware, authSlice} = auth.generateAuthSlice(
|
|
63
|
-
// biome-ignore lint/suspicious/noExplicitAny: Test mock
|
|
64
|
-
api as any
|
|
65
|
-
);
|
|
72
|
+
const {authReducer, middleware, authSlice} = auth.generateAuthSlice(api as any);
|
|
66
73
|
const store = configureStore({
|
|
67
74
|
middleware: (getDefault) =>
|
|
68
75
|
getDefault({serializableCheck: false}).concat(api.middleware, ...middleware),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// biome-ignore-all lint/suspicious/noExplicitAny: test mock typing
|
|
1
2
|
import {beforeEach, describe, expect, it, mock} from "bun:test";
|
|
2
3
|
import {configureStore} from "@reduxjs/toolkit";
|
|
3
4
|
import {
|
|
@@ -79,7 +80,6 @@ describe("generateBetterAuthSlice", () => {
|
|
|
79
80
|
});
|
|
80
81
|
|
|
81
82
|
it("creates a slice with initial state", () => {
|
|
82
|
-
// biome-ignore lint/suspicious/noExplicitAny: Mock client type
|
|
83
83
|
const betterAuthSlice = generateBetterAuthSlice({authClient: mockAuthClient as any});
|
|
84
84
|
|
|
85
85
|
const initialState = betterAuthSlice.reducer(undefined, {type: "@@INIT"});
|
|
@@ -92,7 +92,6 @@ describe("generateBetterAuthSlice", () => {
|
|
|
92
92
|
});
|
|
93
93
|
|
|
94
94
|
it("setSession action updates state correctly", () => {
|
|
95
|
-
// biome-ignore lint/suspicious/noExplicitAny: Mock client type
|
|
96
95
|
const betterAuthSlice = generateBetterAuthSlice({authClient: mockAuthClient as any});
|
|
97
96
|
|
|
98
97
|
const user: BetterAuthUser = {
|
|
@@ -118,7 +117,6 @@ describe("generateBetterAuthSlice", () => {
|
|
|
118
117
|
});
|
|
119
118
|
|
|
120
119
|
it("clearSession action resets state", () => {
|
|
121
|
-
// biome-ignore lint/suspicious/noExplicitAny: Mock client type
|
|
122
120
|
const betterAuthSlice = generateBetterAuthSlice({authClient: mockAuthClient as any});
|
|
123
121
|
|
|
124
122
|
// First set a session
|
|
@@ -147,7 +145,6 @@ describe("generateBetterAuthSlice", () => {
|
|
|
147
145
|
});
|
|
148
146
|
|
|
149
147
|
it("setLoading action updates loading state", () => {
|
|
150
|
-
// biome-ignore lint/suspicious/noExplicitAny: Mock client type
|
|
151
148
|
const betterAuthSlice = generateBetterAuthSlice({authClient: mockAuthClient as any});
|
|
152
149
|
|
|
153
150
|
let state = betterAuthSlice.reducer(undefined, betterAuthSlice.actions.setLoading(true));
|
|
@@ -158,7 +155,6 @@ describe("generateBetterAuthSlice", () => {
|
|
|
158
155
|
});
|
|
159
156
|
|
|
160
157
|
it("setError action updates error state", () => {
|
|
161
|
-
// biome-ignore lint/suspicious/noExplicitAny: Mock client type
|
|
162
158
|
const betterAuthSlice = generateBetterAuthSlice({authClient: mockAuthClient as any});
|
|
163
159
|
|
|
164
160
|
const state = betterAuthSlice.reducer(
|
|
@@ -171,7 +167,6 @@ describe("generateBetterAuthSlice", () => {
|
|
|
171
167
|
});
|
|
172
168
|
|
|
173
169
|
it("logout action clears session state", () => {
|
|
174
|
-
// biome-ignore lint/suspicious/noExplicitAny: Mock client type
|
|
175
170
|
const betterAuthSlice = generateBetterAuthSlice({authClient: mockAuthClient as any});
|
|
176
171
|
|
|
177
172
|
// First set a session
|
|
@@ -200,7 +195,6 @@ describe("generateBetterAuthSlice", () => {
|
|
|
200
195
|
});
|
|
201
196
|
|
|
202
197
|
it("returns middleware array", () => {
|
|
203
|
-
// biome-ignore lint/suspicious/noExplicitAny: Mock client type
|
|
204
198
|
const betterAuthSlice = generateBetterAuthSlice({authClient: mockAuthClient as any});
|
|
205
199
|
|
|
206
200
|
expect(Array.isArray(betterAuthSlice.middleware)).toBe(true);
|
|
@@ -208,20 +202,15 @@ describe("generateBetterAuthSlice", () => {
|
|
|
208
202
|
});
|
|
209
203
|
|
|
210
204
|
it("returns authClient reference", () => {
|
|
211
|
-
// biome-ignore lint/suspicious/noExplicitAny: Mock client type
|
|
212
205
|
const betterAuthSlice = generateBetterAuthSlice({authClient: mockAuthClient as any});
|
|
213
206
|
|
|
214
|
-
// biome-ignore lint/suspicious/noExplicitAny: Mock client type comparison
|
|
215
207
|
expect(betterAuthSlice.authClient).toBe(mockAuthClient as any);
|
|
216
208
|
});
|
|
217
209
|
|
|
218
210
|
it("syncSession function updates state from auth client", async () => {
|
|
219
|
-
// biome-ignore lint/suspicious/noExplicitAny: Mock client type
|
|
220
211
|
const betterAuthSlice = generateBetterAuthSlice({authClient: mockAuthClient as any});
|
|
221
212
|
|
|
222
|
-
// biome-ignore lint/suspicious/noExplicitAny: Test mock for dispatched actions
|
|
223
213
|
const dispatchedActions: any[] = [];
|
|
224
|
-
// biome-ignore lint/suspicious/noExplicitAny: Test mock dispatch function
|
|
225
214
|
const mockDispatch = (action: any) => {
|
|
226
215
|
dispatchedActions.push(action);
|
|
227
216
|
};
|
|
@@ -242,12 +231,9 @@ describe("generateBetterAuthSlice", () => {
|
|
|
242
231
|
},
|
|
243
232
|
})
|
|
244
233
|
) as unknown as typeof mockAuthClient.getSession;
|
|
245
|
-
// biome-ignore lint/suspicious/noExplicitAny: Mock client type
|
|
246
234
|
const betterAuthSlice = generateBetterAuthSlice({authClient: mockAuthClient as any});
|
|
247
235
|
|
|
248
|
-
// biome-ignore lint/suspicious/noExplicitAny: Test mock for dispatched actions
|
|
249
236
|
const dispatchedActions: any[] = [];
|
|
250
|
-
// biome-ignore lint/suspicious/noExplicitAny: Test mock dispatch function
|
|
251
237
|
const mockDispatch = (action: any) => {
|
|
252
238
|
dispatchedActions.push(action);
|
|
253
239
|
};
|
|
@@ -266,12 +252,9 @@ describe("generateBetterAuthSlice", () => {
|
|
|
266
252
|
) as unknown as typeof mockAuthClient.getSession;
|
|
267
253
|
const originalConsoleError = console.error;
|
|
268
254
|
console.error = mock(() => {});
|
|
269
|
-
// biome-ignore lint/suspicious/noExplicitAny: Mock client type
|
|
270
255
|
const betterAuthSlice = generateBetterAuthSlice({authClient: mockAuthClient as any});
|
|
271
256
|
|
|
272
|
-
// biome-ignore lint/suspicious/noExplicitAny: Test mock for dispatched actions
|
|
273
257
|
const dispatchedActions: any[] = [];
|
|
274
|
-
// biome-ignore lint/suspicious/noExplicitAny: Test mock dispatch function
|
|
275
258
|
const mockDispatch = (action: any) => {
|
|
276
259
|
dispatchedActions.push(action);
|
|
277
260
|
};
|
|
@@ -287,7 +270,6 @@ describe("generateBetterAuthSlice", () => {
|
|
|
287
270
|
});
|
|
288
271
|
|
|
289
272
|
it("logout middleware calls signOut for slice logout action", async () => {
|
|
290
|
-
// biome-ignore lint/suspicious/noExplicitAny: Mock client type
|
|
291
273
|
const betterAuthSlice = generateBetterAuthSlice({authClient: mockAuthClient as any});
|
|
292
274
|
const store = configureStore({
|
|
293
275
|
middleware: (getDefaultMiddleware) =>
|
|
@@ -312,7 +294,6 @@ describe("generateBetterAuthSlice", () => {
|
|
|
312
294
|
console.error = (...args: unknown[]): void => {
|
|
313
295
|
errorCalls.push(args);
|
|
314
296
|
};
|
|
315
|
-
// biome-ignore lint/suspicious/noExplicitAny: Mock client type
|
|
316
297
|
const betterAuthSlice = generateBetterAuthSlice({authClient: mockAuthClient as any});
|
|
317
298
|
const store = configureStore({
|
|
318
299
|
middleware: (getDefaultMiddleware) =>
|
|
@@ -342,7 +323,6 @@ describe("generateBetterAuthSlice", () => {
|
|
|
342
323
|
console.error = (...args: unknown[]): void => {
|
|
343
324
|
errorCalls.push(args);
|
|
344
325
|
};
|
|
345
|
-
// biome-ignore lint/suspicious/noExplicitAny: Mock client type
|
|
346
326
|
const betterAuthSlice = generateBetterAuthSlice({authClient: mockAuthClient as any});
|
|
347
327
|
const store = configureStore({
|
|
348
328
|
middleware: (getDefaultMiddleware) =>
|
|
@@ -364,7 +344,6 @@ describe("generateBetterAuthSlice", () => {
|
|
|
364
344
|
});
|
|
365
345
|
|
|
366
346
|
it("global auth/logout action signs out and clears session", async () => {
|
|
367
|
-
// biome-ignore lint/suspicious/noExplicitAny: Mock client type
|
|
368
347
|
const betterAuthSlice = generateBetterAuthSlice({authClient: mockAuthClient as any});
|
|
369
348
|
const store = configureStore({
|
|
370
349
|
middleware: (getDefaultMiddleware) =>
|
|
@@ -400,7 +379,6 @@ describe("generateBetterAuthSlice", () => {
|
|
|
400
379
|
});
|
|
401
380
|
|
|
402
381
|
describe("Better Auth selectors", () => {
|
|
403
|
-
// biome-ignore lint/suspicious/noExplicitAny: Test mock state factory
|
|
404
382
|
const createMockState = (betterAuth: Partial<BetterAuthState> = {}): any => ({
|
|
405
383
|
betterAuth: {
|
|
406
384
|
error: null,
|
|
@@ -449,7 +427,6 @@ describe("Better Auth selectors", () => {
|
|
|
449
427
|
});
|
|
450
428
|
|
|
451
429
|
it("selectors handle missing betterAuth state gracefully", () => {
|
|
452
|
-
// biome-ignore lint/suspicious/noExplicitAny: Test empty state for selector edge case
|
|
453
430
|
const emptyState = {} as any;
|
|
454
431
|
|
|
455
432
|
expect(selectBetterAuthIsAuthenticated(emptyState)).toBe(false);
|
|
@@ -461,7 +438,6 @@ describe("Better Auth selectors", () => {
|
|
|
461
438
|
|
|
462
439
|
it("selectBetterAuthState returns the raw betterAuth slice of state", () => {
|
|
463
440
|
expect(selectBetterAuthState(createMockState({userId: "user-zzz"}))?.userId).toBe("user-zzz");
|
|
464
|
-
// biome-ignore lint/suspicious/noExplicitAny: Test empty state
|
|
465
441
|
expect(selectBetterAuthState({} as any)).toBeUndefined();
|
|
466
442
|
});
|
|
467
443
|
});
|
package/src/buildNumber.test.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import {afterAll, beforeEach, describe, expect, it} from "bun:test";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as path from "node:path";
|
|
2
5
|
|
|
3
6
|
import {coerceBuildNumber, resolveBuildNumber} from "./buildNumber";
|
|
4
7
|
|
|
@@ -117,4 +120,20 @@ describe("resolveBuildNumber", () => {
|
|
|
117
120
|
process.env.EXPO_PUBLIC_BUILD_NUMBER = "999";
|
|
118
121
|
expect(resolveBuildNumber({override: 1})).toBe(1);
|
|
119
122
|
});
|
|
123
|
+
|
|
124
|
+
it("returns undefined when git rev-list fails (no git, detached HEAD, etc.)", () => {
|
|
125
|
+
// Use a freshly-created mkdtemp directory that we know is not inside any git
|
|
126
|
+
// worktree (parents won't be a repo because we create it under the OS temp dir
|
|
127
|
+
// and no parent has a `.git` folder). This makes `git rev-list --count HEAD`
|
|
128
|
+
// exit non-zero so execSync throws, exercising the catch branch.
|
|
129
|
+
const originalCwd = process.cwd();
|
|
130
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "rtk-buildnumber-no-git-"));
|
|
131
|
+
process.chdir(tmpDir);
|
|
132
|
+
try {
|
|
133
|
+
expect(resolveBuildNumber()).toBeUndefined();
|
|
134
|
+
} finally {
|
|
135
|
+
process.chdir(originalCwd);
|
|
136
|
+
fs.rmSync(tmpDir, {force: true, recursive: true});
|
|
137
|
+
}
|
|
138
|
+
});
|
|
120
139
|
});
|
package/src/constants.test.ts
CHANGED
|
@@ -5,9 +5,11 @@ import {
|
|
|
5
5
|
baseTasksUrl,
|
|
6
6
|
baseUrl,
|
|
7
7
|
baseWebsocketsUrl,
|
|
8
|
+
isWebsocketsDebugEnabled,
|
|
8
9
|
logAuth,
|
|
9
10
|
logSocket,
|
|
10
11
|
resolveBaseUrls,
|
|
12
|
+
setRealtimeDebug,
|
|
11
13
|
} from "./constants";
|
|
12
14
|
|
|
13
15
|
describe("resolveBaseUrls", () => {
|
|
@@ -154,7 +156,7 @@ describe("module-level exports", () => {
|
|
|
154
156
|
});
|
|
155
157
|
});
|
|
156
158
|
|
|
157
|
-
describe("logAuth
|
|
159
|
+
describe("logAuth", () => {
|
|
158
160
|
const originalDebug = console.debug;
|
|
159
161
|
const calls: unknown[][] = [];
|
|
160
162
|
|
|
@@ -173,6 +175,22 @@ describe("logAuth / logSocket", () => {
|
|
|
173
175
|
logAuth("auth message");
|
|
174
176
|
expect(calls).toEqual([]);
|
|
175
177
|
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe("logSocket", () => {
|
|
181
|
+
const originalInfo = console.info;
|
|
182
|
+
const calls: unknown[][] = [];
|
|
183
|
+
|
|
184
|
+
beforeEach(() => {
|
|
185
|
+
calls.length = 0;
|
|
186
|
+
console.info = (...args: unknown[]): void => {
|
|
187
|
+
calls.push(args);
|
|
188
|
+
};
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
afterEach(() => {
|
|
192
|
+
console.info = originalInfo;
|
|
193
|
+
});
|
|
176
194
|
|
|
177
195
|
it("logSocket logs when passed boolean true", () => {
|
|
178
196
|
logSocket(true, "socket message");
|
|
@@ -200,5 +218,40 @@ describe("logAuth / logSocket", () => {
|
|
|
200
218
|
});
|
|
201
219
|
});
|
|
202
220
|
|
|
221
|
+
describe("setRealtimeDebug / isWebsocketsDebugEnabled", () => {
|
|
222
|
+
const originalInfo = console.info;
|
|
223
|
+
afterEach(() => {
|
|
224
|
+
console.info = originalInfo;
|
|
225
|
+
setRealtimeDebug(false);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("isWebsocketsDebugEnabled returns false by default", () => {
|
|
229
|
+
expect(isWebsocketsDebugEnabled()).toBe(false);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("setRealtimeDebug(true) makes isWebsocketsDebugEnabled return true", () => {
|
|
233
|
+
setRealtimeDebug(true);
|
|
234
|
+
expect(isWebsocketsDebugEnabled()).toBe(true);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("setRealtimeDebug(false) disables runtime debug", () => {
|
|
238
|
+
setRealtimeDebug(true);
|
|
239
|
+
expect(isWebsocketsDebugEnabled()).toBe(true);
|
|
240
|
+
setRealtimeDebug(false);
|
|
241
|
+
expect(isWebsocketsDebugEnabled()).toBe(false);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("logSocket logs when runtime websocket debug is enabled via setRealtimeDebug", () => {
|
|
245
|
+
const calls: unknown[][] = [];
|
|
246
|
+
console.info = (...args: unknown[]): void => {
|
|
247
|
+
calls.push(args);
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
setRealtimeDebug(true);
|
|
251
|
+
logSocket(undefined, "runtime debug message");
|
|
252
|
+
expect(calls).toEqual([["[websocket]", "runtime debug message"]]);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
203
256
|
// Mock.module tests (expo tunnel warning, AUTH_DEBUG) moved to
|
|
204
257
|
// src/isolated/constants.isolated.ts to avoid coverage tracking interference.
|
package/src/constants.ts
CHANGED
|
@@ -17,12 +17,23 @@ export const logAuth = (...args: string[]): void => {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
// Handy debug logging socket events, but not enabled by default.
|
|
20
|
-
// Can also be enabled by user feature flag.
|
|
20
|
+
// Can also be enabled by user feature flag or runtime via setRealtimeDebug (admin config).
|
|
21
21
|
const WEBSOCKETS_DEBUG = Constants.expoConfig?.extra?.WEBSOCKETS_DEBUG === "true";
|
|
22
22
|
if (WEBSOCKETS_DEBUG) {
|
|
23
23
|
console.debug("WEBSOCKETS_DEBUG is enabled");
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
let runtimeWebsocketsDebug = false;
|
|
27
|
+
|
|
28
|
+
/** Enable websocket debug logging at runtime (e.g. from admin debug.websocketsDebug). */
|
|
29
|
+
export const setRealtimeDebug = (enabled: boolean): void => {
|
|
30
|
+
runtimeWebsocketsDebug = enabled;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const isWebsocketsDebugEnabled = (): boolean => {
|
|
34
|
+
return WEBSOCKETS_DEBUG || runtimeWebsocketsDebug;
|
|
35
|
+
};
|
|
36
|
+
|
|
26
37
|
// Handy debug logging for websockets, enabled by user.featureFlags.debugWebsockets.enabled or passing in true.
|
|
27
38
|
export const logSocket = (
|
|
28
39
|
user?: {featureFlags?: {debugWebsockets?: {enabled?: boolean}}} | boolean,
|
|
@@ -31,9 +42,9 @@ export const logSocket = (
|
|
|
31
42
|
if (
|
|
32
43
|
typeof user === "boolean"
|
|
33
44
|
? user
|
|
34
|
-
: user?.featureFlags?.debugWebsockets?.enabled ||
|
|
45
|
+
: user?.featureFlags?.debugWebsockets?.enabled || isWebsocketsDebugEnabled()
|
|
35
46
|
) {
|
|
36
|
-
console.
|
|
47
|
+
console.info("[websocket]", ...args);
|
|
37
48
|
}
|
|
38
49
|
};
|
|
39
50
|
|