@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.
Files changed (147) hide show
  1. package/dist/authSlice.test.js +2 -10
  2. package/dist/authSlice.test.js.map +1 -1
  3. package/dist/authSliceNative.test.js +11 -3
  4. package/dist/authSliceNative.test.js.map +1 -1
  5. package/dist/betterAuthSlice.test.js +1 -25
  6. package/dist/betterAuthSlice.test.js.map +1 -1
  7. package/dist/buildNumber.test.js +19 -0
  8. package/dist/buildNumber.test.js.map +1 -1
  9. package/dist/constants.d.ts +3 -0
  10. package/dist/constants.d.ts.map +1 -1
  11. package/dist/constants.js +11 -3
  12. package/dist/constants.js.map +1 -1
  13. package/dist/constants.test.js +44 -2
  14. package/dist/constants.test.js.map +1 -1
  15. package/dist/emptyApi.d.ts +2 -0
  16. package/dist/emptyApi.d.ts.map +1 -1
  17. package/dist/emptyApi.js +5 -0
  18. package/dist/emptyApi.js.map +1 -1
  19. package/dist/index.d.ts +8 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +8 -0
  22. package/dist/index.js.map +1 -1
  23. package/dist/isolated/constants.isolated.js +7 -1
  24. package/dist/isolated/constants.isolated.js.map +1 -1
  25. package/dist/isolated/offlineMiddleware.isolated.d.ts +2 -0
  26. package/dist/isolated/offlineMiddleware.isolated.d.ts.map +1 -0
  27. package/dist/isolated/offlineMiddleware.isolated.js +516 -0
  28. package/dist/isolated/offlineMiddleware.isolated.js.map +1 -0
  29. package/dist/isolated/offlineMiddleware.native.isolated.d.ts +2 -0
  30. package/dist/isolated/offlineMiddleware.native.isolated.d.ts.map +1 -0
  31. package/dist/isolated/offlineMiddleware.native.isolated.js +70 -0
  32. package/dist/isolated/offlineMiddleware.native.isolated.js.map +1 -0
  33. package/dist/isolated/useUpgradeCheck.mobile.isolated.d.ts +2 -0
  34. package/dist/isolated/useUpgradeCheck.mobile.isolated.d.ts.map +1 -0
  35. package/dist/{useUpgradeCheck.mobile.test.js → isolated/useUpgradeCheck.mobile.isolated.js} +13 -5
  36. package/dist/isolated/useUpgradeCheck.mobile.isolated.js.map +1 -0
  37. package/dist/isolated/useUpgradeCheck.web.isolated.d.ts +2 -0
  38. package/dist/isolated/useUpgradeCheck.web.isolated.d.ts.map +1 -0
  39. package/dist/{useUpgradeCheck.test.js → isolated/useUpgradeCheck.web.isolated.js} +37 -7
  40. package/dist/isolated/useUpgradeCheck.web.isolated.js.map +1 -0
  41. package/dist/mongooseSlice.test.js +1 -1
  42. package/dist/mongooseSlice.test.js.map +1 -1
  43. package/dist/offlineGate.d.ts +8 -0
  44. package/dist/offlineGate.d.ts.map +1 -0
  45. package/dist/offlineGate.js +20 -0
  46. package/dist/offlineGate.js.map +1 -0
  47. package/dist/offlineGate.test.d.ts +2 -0
  48. package/dist/offlineGate.test.d.ts.map +1 -0
  49. package/dist/offlineGate.test.js +46 -0
  50. package/dist/offlineGate.test.js.map +1 -0
  51. package/dist/offlineMiddleware.d.ts +46 -0
  52. package/dist/offlineMiddleware.d.ts.map +1 -0
  53. package/dist/offlineMiddleware.js +591 -0
  54. package/dist/offlineMiddleware.js.map +1 -0
  55. package/dist/offlineMiddleware.test.d.ts +2 -0
  56. package/dist/offlineMiddleware.test.d.ts.map +1 -0
  57. package/dist/offlineMiddleware.test.js +145 -0
  58. package/dist/offlineMiddleware.test.js.map +1 -0
  59. package/dist/offlineSlice.d.ts +232 -0
  60. package/dist/offlineSlice.d.ts.map +1 -0
  61. package/dist/offlineSlice.js +65 -0
  62. package/dist/offlineSlice.js.map +1 -0
  63. package/dist/offlineSlice.test.d.ts +2 -0
  64. package/dist/offlineSlice.test.d.ts.map +1 -0
  65. package/dist/offlineSlice.test.js +153 -0
  66. package/dist/offlineSlice.test.js.map +1 -0
  67. package/dist/realtime.d.ts +123 -0
  68. package/dist/realtime.d.ts.map +1 -0
  69. package/dist/realtime.js +299 -0
  70. package/dist/realtime.js.map +1 -0
  71. package/dist/realtime.test.d.ts +2 -0
  72. package/dist/realtime.test.d.ts.map +1 -0
  73. package/dist/realtime.test.js +339 -0
  74. package/dist/realtime.test.js.map +1 -0
  75. package/dist/socket.d.ts.map +1 -1
  76. package/dist/socket.js +29 -28
  77. package/dist/socket.js.map +1 -1
  78. package/dist/sync.d.ts +35 -0
  79. package/dist/sync.d.ts.map +1 -0
  80. package/dist/sync.js +205 -0
  81. package/dist/sync.js.map +1 -0
  82. package/dist/tagGenerator.d.ts +20 -1
  83. package/dist/tagGenerator.d.ts.map +1 -1
  84. package/dist/tagGenerator.js +1 -2
  85. package/dist/tagGenerator.js.map +1 -1
  86. package/dist/useOfflineStatus.d.ts +33 -0
  87. package/dist/useOfflineStatus.d.ts.map +1 -0
  88. package/dist/useOfflineStatus.js +43 -0
  89. package/dist/useOfflineStatus.js.map +1 -0
  90. package/dist/useOfflineStatus.test.d.ts +2 -0
  91. package/dist/useOfflineStatus.test.d.ts.map +1 -0
  92. package/dist/useOfflineStatus.test.js +119 -0
  93. package/dist/useOfflineStatus.test.js.map +1 -0
  94. package/dist/useRealtimeDebug.d.ts +6 -0
  95. package/dist/useRealtimeDebug.d.ts.map +1 -0
  96. package/dist/useRealtimeDebug.js +41 -0
  97. package/dist/useRealtimeDebug.js.map +1 -0
  98. package/dist/useServerStatus.d.ts +29 -0
  99. package/dist/useServerStatus.d.ts.map +1 -0
  100. package/dist/useServerStatus.js +89 -0
  101. package/dist/useServerStatus.js.map +1 -0
  102. package/dist/useUpgradeCheck.d.ts +7 -3
  103. package/dist/useUpgradeCheck.d.ts.map +1 -1
  104. package/dist/useUpgradeCheck.js +27 -13
  105. package/dist/useUpgradeCheck.js.map +1 -1
  106. package/package.json +2 -2
  107. package/src/authSlice.test.ts +2 -11
  108. package/src/authSliceNative.test.ts +11 -4
  109. package/src/betterAuthSlice.test.ts +1 -25
  110. package/src/buildNumber.test.ts +19 -0
  111. package/src/constants.test.ts +54 -1
  112. package/src/constants.ts +14 -3
  113. package/src/emptyApi.ts +8 -0
  114. package/src/index.ts +8 -0
  115. package/src/isolated/constants.isolated.ts +7 -3
  116. package/src/isolated/offlineMiddleware.isolated.ts +696 -0
  117. package/src/isolated/offlineMiddleware.native.isolated.ts +80 -0
  118. package/src/{useUpgradeCheck.mobile.test.ts → isolated/useUpgradeCheck.mobile.isolated.ts} +13 -4
  119. package/src/{useUpgradeCheck.test.ts → isolated/useUpgradeCheck.web.isolated.ts} +48 -6
  120. package/src/mongooseSlice.test.ts +1 -1
  121. package/src/offlineGate.test.ts +55 -0
  122. package/src/offlineGate.ts +25 -0
  123. package/src/offlineMiddleware.test.ts +187 -0
  124. package/src/offlineMiddleware.ts +718 -0
  125. package/src/offlineSlice.test.ts +204 -0
  126. package/src/offlineSlice.ts +130 -0
  127. package/src/realtime.test.ts +404 -0
  128. package/src/realtime.ts +408 -0
  129. package/src/socket.ts +33 -32
  130. package/src/sync.ts +310 -0
  131. package/src/tagGenerator.ts +29 -11
  132. package/src/useOfflineStatus.test.ts +157 -0
  133. package/src/useOfflineStatus.ts +82 -0
  134. package/src/useRealtimeDebug.ts +51 -0
  135. package/src/useServerStatus.ts +122 -0
  136. package/src/useUpgradeCheck.ts +35 -13
  137. package/dist/isolated/useUpgradeCheck.isolated.d.ts +0 -2
  138. package/dist/isolated/useUpgradeCheck.isolated.d.ts.map +0 -1
  139. package/dist/isolated/useUpgradeCheck.isolated.js +0 -153
  140. package/dist/isolated/useUpgradeCheck.isolated.js.map +0 -1
  141. package/dist/useUpgradeCheck.mobile.test.d.ts +0 -2
  142. package/dist/useUpgradeCheck.mobile.test.d.ts.map +0 -1
  143. package/dist/useUpgradeCheck.mobile.test.js.map +0 -1
  144. package/dist/useUpgradeCheck.test.d.ts +0 -2
  145. package/dist/useUpgradeCheck.test.d.ts.map +0 -1
  146. package/dist/useUpgradeCheck.test.js.map +0 -1
  147. 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
- * By default a single check runs on mount. Pass `pollingIntervalMs` and/or
33
- * `recheckOnForeground` to keep long-lived sessions up to date.
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":"AAOA,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;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,eAAe,GAAI,UAAU,sBAAsB,KAAG,qBA0GlE,CAAC"}
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"}
@@ -1,6 +1,10 @@
1
1
  import Constants from "expo-constants";
2
2
  import { useCallback, useEffect, useRef, useState } from "react";
3
- import { AppState, Linking } from "react-native";
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
- * By default a single check runs on mount. Pass `pollingIntervalMs` and/or
16
- * `recheckOnForeground` to keep long-lived sessions up to date.
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 at the configured interval
98
+ // Periodic re-check using the server-driven interval (falls back to the local prop until first response).
85
99
  useEffect(() => {
86
- if (!pollingIntervalMs) {
100
+ if (!activePollingIntervalMs) {
87
101
  return;
88
102
  }
89
- const interval = setInterval(runCheck, pollingIntervalMs);
103
+ const interval = setInterval(runCheck, activePollingIntervalMs);
90
104
  return () => clearInterval(interval);
91
- }, [runCheck, pollingIntervalMs]);
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":"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"}
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.13.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.13.0"
70
+ "version": "0.14.0"
71
71
  }
@@ -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
  });
@@ -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
  });
@@ -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 / logSocket", () => {
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 || WEBSOCKETS_DEBUG
45
+ : user?.featureFlags?.debugWebsockets?.enabled || isWebsocketsDebugEnabled()
35
46
  ) {
36
- console.debug(`[websocket]`, ...args);
47
+ console.info("[websocket]", ...args);
37
48
  }
38
49
  };
39
50