quidproquo-web-react 0.1.0 → 0.1.2
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/lib/commonjs/auth/hooks/useRefreshTokens.js +5 -3
- package/lib/commonjs/dateTime/DateTime.js +6 -14
- package/lib/commonjs/hooks/asmj/createQpqRuntimeDefinition.js +1 -1
- package/lib/commonjs/hooks/asmj/index.d.ts +1 -1
- package/lib/commonjs/hooks/asmj/useQpqRuntime.js +1 -0
- package/lib/commonjs/hooks/useAsyncEffect.js +3 -0
- package/lib/commonjs/hooks/useQpq.js +10 -3
- package/lib/commonjs/hooks/useRunEvery.js +6 -2
- package/lib/commonjs/hooks/useThrottledMemo.js +3 -0
- package/lib/commonjs/useFieldBinding/useFieldBinding.js +3 -3
- package/lib/commonjs/webSocketQueue/hooks/actionProcessor/getServiceRequestActionProcessor.js +1 -1
- package/lib/commonjs/webSocketQueue/hooks/useQpqWebsocketQueueRuntime.d.ts +1 -6
- package/lib/commonjs/websocket/WebSocketAuthSync/WebSocketAuthSync.js +3 -3
- package/lib/commonjs/websocket/WebSocketAuthSync/hooks/useWebsocketAuthSync.js +1 -1
- package/lib/esm/auth/hooks/useRefreshTokens.js +5 -3
- package/lib/esm/dateTime/DateTime.js +6 -14
- package/lib/esm/hooks/asmj/createQpqRuntimeDefinition.js +1 -1
- package/lib/esm/hooks/asmj/index.d.ts +1 -1
- package/lib/esm/hooks/asmj/index.js +1 -1
- package/lib/esm/hooks/asmj/useQpqRuntime.js +1 -0
- package/lib/esm/hooks/useAsyncEffect.js +3 -0
- package/lib/esm/hooks/useQpq.js +11 -4
- package/lib/esm/hooks/useRunEvery.js +6 -2
- package/lib/esm/hooks/useThrottledMemo.js +3 -0
- package/lib/esm/useFieldBinding/useFieldBinding.js +3 -3
- package/lib/esm/webSocketQueue/hooks/actionProcessor/getServiceRequestActionProcessor.js +1 -1
- package/lib/esm/webSocketQueue/hooks/useQpqWebsocketQueueRuntime.d.ts +1 -6
- package/lib/esm/webSocketQueue/hooks/useWebsocketQueueSendEvent.js +1 -1
- package/lib/esm/websocket/WebSocketAuthSync/WebSocketAuthSync.js +3 -3
- package/lib/esm/websocket/WebSocketAuthSync/hooks/useWebsocketAuthSync.js +1 -1
- package/package.json +8 -8
|
@@ -5,7 +5,9 @@ const react_1 = require("react");
|
|
|
5
5
|
const useFastCallback_1 = require("../../hooks/useFastCallback");
|
|
6
6
|
const useRefreshTokens = (authenticationInfo, refreshTokens) => {
|
|
7
7
|
const stableRefreshTokens = (0, useFastCallback_1.useFastCallback)(refreshTokens);
|
|
8
|
-
|
|
8
|
+
// Stable identity so the effect below can list it as a dependency without
|
|
9
|
+
// re-running on every render.
|
|
10
|
+
const refresh = (0, useFastCallback_1.useFastCallback)(() => {
|
|
9
11
|
if (authenticationInfo && authenticationInfo.refreshToken && authenticationInfo.expiresAt) {
|
|
10
12
|
const now = new Date().toISOString();
|
|
11
13
|
const timeToExpire = new Date(authenticationInfo.expiresAt).getTime() - new Date(now).getTime();
|
|
@@ -23,7 +25,7 @@ const useRefreshTokens = (authenticationInfo, refreshTokens) => {
|
|
|
23
25
|
}
|
|
24
26
|
}
|
|
25
27
|
return null;
|
|
26
|
-
};
|
|
28
|
+
});
|
|
27
29
|
(0, react_1.useEffect)(() => {
|
|
28
30
|
const timerId = refresh();
|
|
29
31
|
return () => {
|
|
@@ -38,6 +40,6 @@ const useRefreshTokens = (authenticationInfo, refreshTokens) => {
|
|
|
38
40
|
// reference would re-run this effect every render — and when the token is
|
|
39
41
|
// expired/near-expiry the "refresh immediately" branch would then fire a
|
|
40
42
|
// network refresh on every render, an unbounded loop.
|
|
41
|
-
}, [authenticationInfo === null || authenticationInfo === void 0 ? void 0 : authenticationInfo.expiresAt, authenticationInfo === null || authenticationInfo === void 0 ? void 0 : authenticationInfo.refreshToken]);
|
|
43
|
+
}, [refresh, authenticationInfo === null || authenticationInfo === void 0 ? void 0 : authenticationInfo.expiresAt, authenticationInfo === null || authenticationInfo === void 0 ? void 0 : authenticationInfo.refreshToken]);
|
|
42
44
|
};
|
|
43
45
|
exports.useRefreshTokens = useRefreshTokens;
|
|
@@ -27,20 +27,12 @@ const pickOptions = (options, keys) => {
|
|
|
27
27
|
}, {});
|
|
28
28
|
return Object.keys(picked).length > 0 ? picked : undefined;
|
|
29
29
|
};
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
return new Date(props.isoDateTime);
|
|
33
|
-
}
|
|
34
|
-
if ('unixTimestampMs' in props) {
|
|
35
|
-
return new Date(props.unixTimestampMs);
|
|
36
|
-
}
|
|
37
|
-
return props.date;
|
|
38
|
-
};
|
|
39
|
-
const component = (props) => {
|
|
40
|
-
const { locale, dateTimeFormatOptions, showTimeAgo = true, hideDate = false, hideTime = false, hideTimeAgo = false, isMultiline = true, placeholder = '—', className } = props;
|
|
30
|
+
const DateTimeComponent = (props) => {
|
|
31
|
+
const { locale, dateTimeFormatOptions, showTimeAgo = true, hideDate = false, hideTime = false, hideTimeAgo = false, isMultiline = true, placeholder = '—', className, } = props;
|
|
41
32
|
// Stable primitive key so a fresh props object doesn't churn the memo.
|
|
33
|
+
// Both the ISO string and the epoch-ms number are valid Date constructor inputs.
|
|
42
34
|
const dateKey = 'isoDateTime' in props ? props.isoDateTime : 'unixTimestampMs' in props ? props.unixTimestampMs : props.date.getTime();
|
|
43
|
-
const date = (0, react_1.useMemo)(() =>
|
|
35
|
+
const date = (0, react_1.useMemo)(() => new Date(dateKey), [dateKey]);
|
|
44
36
|
const isValidDate = !Number.isNaN(date.getTime());
|
|
45
37
|
const formattedDate = (0, react_1.useMemo)(() => { var _a; return (hideDate ? '' : date.toLocaleDateString(locale, (_a = pickOptions(dateTimeFormatOptions, DATE_OPTION_KEYS)) !== null && _a !== void 0 ? _a : DEFAULT_DATE_OPTIONS)); }, [date, locale, dateTimeFormatOptions, hideDate]);
|
|
46
38
|
const formattedTime = (0, react_1.useMemo)(() => { var _a; return (hideTime ? '' : date.toLocaleTimeString(locale, (_a = pickOptions(dateTimeFormatOptions, TIME_OPTION_KEYS)) !== null && _a !== void 0 ? _a : DEFAULT_TIME_OPTIONS)); }, [date, locale, dateTimeFormatOptions, hideTime]);
|
|
@@ -52,6 +44,6 @@ const component = (props) => {
|
|
|
52
44
|
if (!isValidDate) {
|
|
53
45
|
return (0, jsx_runtime_1.jsx)("time", { className: className, children: placeholder });
|
|
54
46
|
}
|
|
55
|
-
return ((0, jsx_runtime_1.jsx)("time", { dateTime: date.toISOString(), title: date.toLocaleString(locale),
|
|
47
|
+
return ((0, jsx_runtime_1.jsx)("time", { className: className, dateTime: date.toISOString(), title: date.toLocaleString(locale), children: isMultiline ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [formattedDateTime ? (0, jsx_runtime_1.jsx)("div", { children: formattedDateTime }) : null, displayTimeAgo ? (0, jsx_runtime_1.jsx)("div", { children: timeAgo }) : null] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [formattedDateTime, displayTimeAgo ? ` (${timeAgo})` : ''] })) }));
|
|
56
48
|
};
|
|
57
|
-
exports.DateTime = (0, react_1.memo)(
|
|
49
|
+
exports.DateTime = (0, react_1.memo)(DateTimeComponent);
|
|
@@ -38,7 +38,7 @@ function useQpqRuntimeState(atom, name) {
|
|
|
38
38
|
const info = atom(name);
|
|
39
39
|
info.state = newState;
|
|
40
40
|
setState(newState);
|
|
41
|
-
}, [setState]);
|
|
41
|
+
}, [atom, name, setState]);
|
|
42
42
|
const getState = (0, react_1.useCallback)(() => {
|
|
43
43
|
const state = atom(name).state;
|
|
44
44
|
return state;
|
|
@@ -31,6 +31,9 @@ const useAsyncEffect = (effect, deps) => {
|
|
|
31
31
|
}
|
|
32
32
|
});
|
|
33
33
|
};
|
|
34
|
+
// The dependency list is supplied by the caller, so it can't be statically
|
|
35
|
+
// verified here — this hook is a passthrough wrapper around useEffect.
|
|
36
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
34
37
|
}, deps);
|
|
35
38
|
};
|
|
36
39
|
exports.useAsyncEffect = useAsyncEffect;
|
|
@@ -15,6 +15,7 @@ const quidproquo_core_1 = require("quidproquo-core");
|
|
|
15
15
|
const react_1 = require("react");
|
|
16
16
|
const actionProcessor_1 = require("../actionProcessor");
|
|
17
17
|
const QpqContextProvider_1 = require("./asmj/QpqContextProvider");
|
|
18
|
+
const useEffectCallback_1 = require("./useEffectCallback");
|
|
18
19
|
function* withVersionCheck(story, versionRef, capturedVersion) {
|
|
19
20
|
let nextValue = undefined;
|
|
20
21
|
while (true) {
|
|
@@ -41,18 +42,24 @@ function useQpq(getActionProcessors = () => __awaiter(this, void 0, void 0, func
|
|
|
41
42
|
const contextGetActionProcessors = (0, actionProcessor_1.useActionProcessors)();
|
|
42
43
|
const versionRef = (0, react_1.useRef)(0);
|
|
43
44
|
(0, react_1.useEffect)(() => {
|
|
45
|
+
const capturedVersionRef = versionRef;
|
|
44
46
|
return () => {
|
|
45
|
-
|
|
47
|
+
capturedVersionRef.current++;
|
|
46
48
|
};
|
|
47
49
|
}, []);
|
|
50
|
+
// Stable identities so the runtime memo can list them as dependencies
|
|
51
|
+
// without being recreated every render — the runtime always calls the
|
|
52
|
+
// latest processors through these wrappers.
|
|
53
|
+
const stableContextGetActionProcessors = (0, useEffectCallback_1.useEffectCallback)(contextGetActionProcessors);
|
|
54
|
+
const stableGetActionProcessors = (0, useEffectCallback_1.useEffectCallback)(getActionProcessors);
|
|
48
55
|
const resolveStory = (0, react_1.useMemo)(() => (0, quidproquo_core_1.createRuntime)([(0, quidproquo_core_1.defineModule)('UI')], {
|
|
49
56
|
depth: 0,
|
|
50
57
|
context: qpqContextValues,
|
|
51
58
|
}, (qpqConfig, dynamicModuleLoader) => __awaiter(this, void 0, void 0, function* () {
|
|
52
|
-
return (Object.assign(Object.assign(Object.assign({}, (yield (0, quidproquo_actionprocessor_web_1.getWebActionProcessors)(qpqConfig, dynamicModuleLoader))), (yield
|
|
59
|
+
return (Object.assign(Object.assign(Object.assign({}, (yield (0, quidproquo_actionprocessor_web_1.getWebActionProcessors)(qpqConfig, dynamicModuleLoader))), (yield stableContextGetActionProcessors(qpqConfig, dynamicModuleLoader))), (yield stableGetActionProcessors(qpqConfig, dynamicModuleLoader))));
|
|
53
60
|
}), () => new Date().toISOString(), logger, `frontend::${'uuid'}`, quidproquo_core_1.QpqRuntimeType.UI, (_runtime) => __awaiter(this, void 0, void 0, function* () {
|
|
54
61
|
// noop
|
|
55
|
-
})), [qpqContextValues]);
|
|
62
|
+
})), [qpqContextValues, stableContextGetActionProcessors, stableGetActionProcessors]);
|
|
56
63
|
const qpq = (0, react_1.useCallback)(function getStoryExecutor(story) {
|
|
57
64
|
return (...args) => __awaiter(this, void 0, void 0, function* () {
|
|
58
65
|
const capturedVersion = versionRef.current;
|
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.useRunEvery = useRunEvery;
|
|
4
4
|
const react_1 = require("react");
|
|
5
|
+
const useEffectCallback_1 = require("./useEffectCallback");
|
|
5
6
|
function useRunEvery(func, intervalSeconds) {
|
|
6
7
|
const [value, setValue] = (0, react_1.useState)(func);
|
|
7
8
|
const intervalRef = (0, react_1.useRef)(null);
|
|
9
|
+
// Stable identity so a caller passing a fresh arrow each render doesn't
|
|
10
|
+
// tear down and recreate the interval.
|
|
11
|
+
const stableFunc = (0, useEffectCallback_1.useEffectCallback)(func);
|
|
8
12
|
(0, react_1.useEffect)(() => {
|
|
9
13
|
intervalRef.current = window.setInterval(() => {
|
|
10
|
-
const newValue =
|
|
14
|
+
const newValue = stableFunc();
|
|
11
15
|
setValue(newValue);
|
|
12
16
|
}, intervalSeconds * 1000);
|
|
13
17
|
return () => {
|
|
@@ -16,6 +20,6 @@ function useRunEvery(func, intervalSeconds) {
|
|
|
16
20
|
intervalRef.current = null;
|
|
17
21
|
}
|
|
18
22
|
};
|
|
19
|
-
}, [intervalSeconds]);
|
|
23
|
+
}, [intervalSeconds, stableFunc]);
|
|
20
24
|
return value;
|
|
21
25
|
}
|
|
@@ -26,6 +26,9 @@ function useThrottledMemo(factory, deps, delaySeconds = 1) {
|
|
|
26
26
|
timeoutRef.current = null;
|
|
27
27
|
}
|
|
28
28
|
};
|
|
29
|
+
// The dependency list is supplied by the caller, so it can't be statically
|
|
30
|
+
// verified here — this hook is a throttled wrapper around useMemo semantics.
|
|
31
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
29
32
|
}, [...deps, delaySeconds]);
|
|
30
33
|
return value;
|
|
31
34
|
}
|
|
@@ -12,9 +12,9 @@ const useFieldBinding = (computedAtom, someSetter, valueExtractor) => {
|
|
|
12
12
|
someSetter(newValue);
|
|
13
13
|
};
|
|
14
14
|
(0, react_1.useEffect)(() => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
// React bails out when the value is unchanged, so no equality guard is
|
|
16
|
+
// needed — and local edits (which also change `value`) don't re-run this.
|
|
17
|
+
setValue(computedValue);
|
|
18
18
|
}, [computedValue]);
|
|
19
19
|
return [value, handleChange];
|
|
20
20
|
};
|
package/lib/commonjs/webSocketQueue/hooks/actionProcessor/getServiceRequestActionProcessor.js
CHANGED
|
@@ -13,7 +13,7 @@ exports.getServiceRequestActionProcessor = void 0;
|
|
|
13
13
|
const quidproquo_core_1 = require("quidproquo-core");
|
|
14
14
|
const quidproquo_webserver_1 = require("quidproquo-webserver");
|
|
15
15
|
const getServiceRequestActionProcessor = (sendEventRef) => ({
|
|
16
|
-
[quidproquo_webserver_1.ServiceActionType.Request]: (_a) => __awaiter(void 0, [_a], void 0, function* ({ serviceName, method, payload
|
|
16
|
+
[quidproquo_webserver_1.ServiceActionType.Request]: (_a) => __awaiter(void 0, [_a], void 0, function* ({ serviceName, method, payload }) {
|
|
17
17
|
var _b;
|
|
18
18
|
const response = yield ((_b = sendEventRef.current) === null || _b === void 0 ? void 0 : _b.call(sendEventRef, { type: `qpq/serviceRequest/${serviceName}/${method}`, payload }));
|
|
19
19
|
if (response && !response.success) {
|
|
@@ -3,9 +3,4 @@ import { AnyWebSocketQueueEventMessageWithCorrelation } from 'quidproquo-webserv
|
|
|
3
3
|
import { QpqRuntimeDefinition } from '../../hooks/asmj/createQpqRuntimeDefinition';
|
|
4
4
|
import { QpqApi, QpqMappedApi } from '../../hooks/asmj/QpqMappedApi';
|
|
5
5
|
import { ActionProcessorListResolverFactory } from '../../hooks/asmj/useQpqRuntime';
|
|
6
|
-
export declare function useQpqWebsocketQueueRuntime<TState, TAction, TApi extends QpqApi>(atom: QpqRuntimeDefinition<TState, TAction, TApi>, mainStory?: Story<any, any>, name?: string, getActionProcessors?: ActionProcessorListResolverFactory<TState>): [
|
|
7
|
-
QpqMappedApi<TApi>,
|
|
8
|
-
TState,
|
|
9
|
-
(action: any) => void,
|
|
10
|
-
(event: Omit<AnyWebSocketQueueEventMessageWithCorrelation, 'correlationId'>) => Promise<any>
|
|
11
|
-
];
|
|
6
|
+
export declare function useQpqWebsocketQueueRuntime<TState, TAction, TApi extends QpqApi>(atom: QpqRuntimeDefinition<TState, TAction, TApi>, mainStory?: Story<any, any>, name?: string, getActionProcessors?: ActionProcessorListResolverFactory<TState>): [QpqMappedApi<TApi>, TState, (action: any) => void, (event: Omit<AnyWebSocketQueueEventMessageWithCorrelation, 'correlationId'>) => Promise<any>];
|
|
@@ -5,10 +5,10 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
5
5
|
const react_1 = require("react");
|
|
6
6
|
const hooks_1 = require("./hooks");
|
|
7
7
|
const WebSocketAuthContext_1 = require("./WebSocketAuthContext");
|
|
8
|
-
const
|
|
8
|
+
const WebSocketAuthSyncComponent = ({ children, accessToken }) => {
|
|
9
9
|
const isAuthenticated = (0, hooks_1.useWebsocketAuthSync)(accessToken);
|
|
10
10
|
// Keep the active websocket alive
|
|
11
11
|
(0, hooks_1.useWebsocketPingPong)();
|
|
12
|
-
return (
|
|
12
|
+
return (0, jsx_runtime_1.jsx)(WebSocketAuthContext_1.WebSocketAuthContext.Provider, { value: isAuthenticated, children: children });
|
|
13
13
|
};
|
|
14
|
-
exports.WebSocketAuthSync = (0, react_1.memo)(
|
|
14
|
+
exports.WebSocketAuthSync = (0, react_1.memo)(WebSocketAuthSyncComponent);
|
|
@@ -34,7 +34,7 @@ const useWebsocketAuthSync = (accessToken) => {
|
|
|
34
34
|
(0, hooks_2.useSubscribeToWebsocket)(quidproquo_web_1.WebsocketServiceEvent.OPEN, updateAuthTokens);
|
|
35
35
|
(0, hooks_2.useSubscribeToWebsocket)(quidproquo_web_1.WebsocketServiceEvent.CLOSE, () => setIsAuthenticated(false));
|
|
36
36
|
// Sync the tokens when they change
|
|
37
|
-
(0, react_1.useEffect)(updateAuthTokens, [accessToken, websocketApi, isAuthenticated]);
|
|
37
|
+
(0, react_1.useEffect)(updateAuthTokens, [updateAuthTokens, accessToken, websocketApi, isAuthenticated]);
|
|
38
38
|
return isAuthenticated;
|
|
39
39
|
};
|
|
40
40
|
exports.useWebsocketAuthSync = useWebsocketAuthSync;
|
|
@@ -2,7 +2,9 @@ import { useEffect } from 'react';
|
|
|
2
2
|
import { useFastCallback } from '../../hooks/useFastCallback';
|
|
3
3
|
export const useRefreshTokens = (authenticationInfo, refreshTokens) => {
|
|
4
4
|
const stableRefreshTokens = useFastCallback(refreshTokens);
|
|
5
|
-
|
|
5
|
+
// Stable identity so the effect below can list it as a dependency without
|
|
6
|
+
// re-running on every render.
|
|
7
|
+
const refresh = useFastCallback(() => {
|
|
6
8
|
if (authenticationInfo && authenticationInfo.refreshToken && authenticationInfo.expiresAt) {
|
|
7
9
|
const now = new Date().toISOString();
|
|
8
10
|
const timeToExpire = new Date(authenticationInfo.expiresAt).getTime() - new Date(now).getTime();
|
|
@@ -20,7 +22,7 @@ export const useRefreshTokens = (authenticationInfo, refreshTokens) => {
|
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
24
|
return null;
|
|
23
|
-
};
|
|
25
|
+
});
|
|
24
26
|
useEffect(() => {
|
|
25
27
|
const timerId = refresh();
|
|
26
28
|
return () => {
|
|
@@ -35,5 +37,5 @@ export const useRefreshTokens = (authenticationInfo, refreshTokens) => {
|
|
|
35
37
|
// reference would re-run this effect every render — and when the token is
|
|
36
38
|
// expired/near-expiry the "refresh immediately" branch would then fire a
|
|
37
39
|
// network refresh on every render, an unbounded loop.
|
|
38
|
-
}, [authenticationInfo?.expiresAt, authenticationInfo?.refreshToken]);
|
|
40
|
+
}, [refresh, authenticationInfo?.expiresAt, authenticationInfo?.refreshToken]);
|
|
39
41
|
};
|
|
@@ -24,20 +24,12 @@ const pickOptions = (options, keys) => {
|
|
|
24
24
|
}, {});
|
|
25
25
|
return Object.keys(picked).length > 0 ? picked : undefined;
|
|
26
26
|
};
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
return new Date(props.isoDateTime);
|
|
30
|
-
}
|
|
31
|
-
if ('unixTimestampMs' in props) {
|
|
32
|
-
return new Date(props.unixTimestampMs);
|
|
33
|
-
}
|
|
34
|
-
return props.date;
|
|
35
|
-
};
|
|
36
|
-
const component = (props) => {
|
|
37
|
-
const { locale, dateTimeFormatOptions, showTimeAgo = true, hideDate = false, hideTime = false, hideTimeAgo = false, isMultiline = true, placeholder = '—', className } = props;
|
|
27
|
+
const DateTimeComponent = (props) => {
|
|
28
|
+
const { locale, dateTimeFormatOptions, showTimeAgo = true, hideDate = false, hideTime = false, hideTimeAgo = false, isMultiline = true, placeholder = '—', className, } = props;
|
|
38
29
|
// Stable primitive key so a fresh props object doesn't churn the memo.
|
|
30
|
+
// Both the ISO string and the epoch-ms number are valid Date constructor inputs.
|
|
39
31
|
const dateKey = 'isoDateTime' in props ? props.isoDateTime : 'unixTimestampMs' in props ? props.unixTimestampMs : props.date.getTime();
|
|
40
|
-
const date = useMemo(() =>
|
|
32
|
+
const date = useMemo(() => new Date(dateKey), [dateKey]);
|
|
41
33
|
const isValidDate = !Number.isNaN(date.getTime());
|
|
42
34
|
const formattedDate = useMemo(() => (hideDate ? '' : date.toLocaleDateString(locale, pickOptions(dateTimeFormatOptions, DATE_OPTION_KEYS) ?? DEFAULT_DATE_OPTIONS)), [date, locale, dateTimeFormatOptions, hideDate]);
|
|
43
35
|
const formattedTime = useMemo(() => (hideTime ? '' : date.toLocaleTimeString(locale, pickOptions(dateTimeFormatOptions, TIME_OPTION_KEYS) ?? DEFAULT_TIME_OPTIONS)), [date, locale, dateTimeFormatOptions, hideTime]);
|
|
@@ -49,6 +41,6 @@ const component = (props) => {
|
|
|
49
41
|
if (!isValidDate) {
|
|
50
42
|
return _jsx("time", { className: className, children: placeholder });
|
|
51
43
|
}
|
|
52
|
-
return (_jsx("time", { dateTime: date.toISOString(), title: date.toLocaleString(locale),
|
|
44
|
+
return (_jsx("time", { className: className, dateTime: date.toISOString(), title: date.toLocaleString(locale), children: isMultiline ? (_jsxs(_Fragment, { children: [formattedDateTime ? _jsx("div", { children: formattedDateTime }) : null, displayTimeAgo ? _jsx("div", { children: timeAgo }) : null] })) : (_jsxs(_Fragment, { children: [formattedDateTime, displayTimeAgo ? ` (${timeAgo})` : ''] })) }));
|
|
53
45
|
};
|
|
54
|
-
export const DateTime = memo(
|
|
46
|
+
export const DateTime = memo(DateTimeComponent);
|
|
@@ -32,7 +32,7 @@ export function useQpqRuntimeState(atom, name) {
|
|
|
32
32
|
const info = atom(name);
|
|
33
33
|
info.state = newState;
|
|
34
34
|
setState(newState);
|
|
35
|
-
}, [setState]);
|
|
35
|
+
}, [atom, name, setState]);
|
|
36
36
|
const getState = useCallback(() => {
|
|
37
37
|
const state = atom(name).state;
|
|
38
38
|
return state;
|
|
@@ -19,5 +19,8 @@ export const useAsyncEffect = (effect, deps) => {
|
|
|
19
19
|
}
|
|
20
20
|
});
|
|
21
21
|
};
|
|
22
|
+
// The dependency list is supplied by the caller, so it can't be statically
|
|
23
|
+
// verified here — this hook is a passthrough wrapper around useEffect.
|
|
24
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
22
25
|
}, deps);
|
|
23
26
|
};
|
package/lib/esm/hooks/useQpq.js
CHANGED
|
@@ -3,6 +3,7 @@ import { createRuntime, defineModule, QpqRuntimeType, } from 'quidproquo-core';
|
|
|
3
3
|
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
4
4
|
import { useActionProcessors } from '../actionProcessor';
|
|
5
5
|
import { useQpqContextValues } from './asmj/QpqContextProvider';
|
|
6
|
+
import { useEffectCallback } from './useEffectCallback';
|
|
6
7
|
function* withVersionCheck(story, versionRef, capturedVersion) {
|
|
7
8
|
let nextValue = undefined;
|
|
8
9
|
while (true) {
|
|
@@ -29,20 +30,26 @@ export function useQpq(getActionProcessors = async () => ({})) {
|
|
|
29
30
|
const contextGetActionProcessors = useActionProcessors();
|
|
30
31
|
const versionRef = useRef(0);
|
|
31
32
|
useEffect(() => {
|
|
33
|
+
const capturedVersionRef = versionRef;
|
|
32
34
|
return () => {
|
|
33
|
-
|
|
35
|
+
capturedVersionRef.current++;
|
|
34
36
|
};
|
|
35
37
|
}, []);
|
|
38
|
+
// Stable identities so the runtime memo can list them as dependencies
|
|
39
|
+
// without being recreated every render — the runtime always calls the
|
|
40
|
+
// latest processors through these wrappers.
|
|
41
|
+
const stableContextGetActionProcessors = useEffectCallback(contextGetActionProcessors);
|
|
42
|
+
const stableGetActionProcessors = useEffectCallback(getActionProcessors);
|
|
36
43
|
const resolveStory = useMemo(() => createRuntime([defineModule('UI')], {
|
|
37
44
|
depth: 0,
|
|
38
45
|
context: qpqContextValues,
|
|
39
46
|
}, async (qpqConfig, dynamicModuleLoader) => ({
|
|
40
47
|
...(await getWebActionProcessors(qpqConfig, dynamicModuleLoader)),
|
|
41
|
-
...(await
|
|
42
|
-
...(await
|
|
48
|
+
...(await stableContextGetActionProcessors(qpqConfig, dynamicModuleLoader)),
|
|
49
|
+
...(await stableGetActionProcessors(qpqConfig, dynamicModuleLoader)),
|
|
43
50
|
}), () => new Date().toISOString(), logger, `frontend::${'uuid'}`, QpqRuntimeType.UI, async (_runtime) => {
|
|
44
51
|
// noop
|
|
45
|
-
}), [qpqContextValues]);
|
|
52
|
+
}), [qpqContextValues, stableContextGetActionProcessors, stableGetActionProcessors]);
|
|
46
53
|
const qpq = useCallback(function getStoryExecutor(story) {
|
|
47
54
|
return async (...args) => {
|
|
48
55
|
const capturedVersion = versionRef.current;
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { useEffectCallback } from './useEffectCallback';
|
|
2
3
|
export function useRunEvery(func, intervalSeconds) {
|
|
3
4
|
const [value, setValue] = useState(func);
|
|
4
5
|
const intervalRef = useRef(null);
|
|
6
|
+
// Stable identity so a caller passing a fresh arrow each render doesn't
|
|
7
|
+
// tear down and recreate the interval.
|
|
8
|
+
const stableFunc = useEffectCallback(func);
|
|
5
9
|
useEffect(() => {
|
|
6
10
|
intervalRef.current = window.setInterval(() => {
|
|
7
|
-
const newValue =
|
|
11
|
+
const newValue = stableFunc();
|
|
8
12
|
setValue(newValue);
|
|
9
13
|
}, intervalSeconds * 1000);
|
|
10
14
|
return () => {
|
|
@@ -13,6 +17,6 @@ export function useRunEvery(func, intervalSeconds) {
|
|
|
13
17
|
intervalRef.current = null;
|
|
14
18
|
}
|
|
15
19
|
};
|
|
16
|
-
}, [intervalSeconds]);
|
|
20
|
+
}, [intervalSeconds, stableFunc]);
|
|
17
21
|
return value;
|
|
18
22
|
}
|
|
@@ -23,6 +23,9 @@ export function useThrottledMemo(factory, deps, delaySeconds = 1) {
|
|
|
23
23
|
timeoutRef.current = null;
|
|
24
24
|
}
|
|
25
25
|
};
|
|
26
|
+
// The dependency list is supplied by the caller, so it can't be statically
|
|
27
|
+
// verified here — this hook is a throttled wrapper around useMemo semantics.
|
|
28
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
26
29
|
}, [...deps, delaySeconds]);
|
|
27
30
|
return value;
|
|
28
31
|
}
|
|
@@ -9,9 +9,9 @@ export const useFieldBinding = (computedAtom, someSetter, valueExtractor) => {
|
|
|
9
9
|
someSetter(newValue);
|
|
10
10
|
};
|
|
11
11
|
useEffect(() => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
// React bails out when the value is unchanged, so no equality guard is
|
|
13
|
+
// needed — and local edits (which also change `value`) don't re-run this.
|
|
14
|
+
setValue(computedValue);
|
|
15
15
|
}, [computedValue]);
|
|
16
16
|
return [value, handleChange];
|
|
17
17
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { actionResult, actionResultError } from 'quidproquo-core';
|
|
2
2
|
import { ServiceActionType } from 'quidproquo-webserver';
|
|
3
3
|
export const getServiceRequestActionProcessor = (sendEventRef) => ({
|
|
4
|
-
[ServiceActionType.Request]: async ({ serviceName, method, payload
|
|
4
|
+
[ServiceActionType.Request]: async ({ serviceName, method, payload }) => {
|
|
5
5
|
const response = await sendEventRef.current?.({ type: `qpq/serviceRequest/${serviceName}/${method}`, payload });
|
|
6
6
|
if (response && !response.success) {
|
|
7
7
|
return actionResultError(response.error.errorType, response.error.errorText, response.error.errorStack);
|
|
@@ -3,9 +3,4 @@ import { AnyWebSocketQueueEventMessageWithCorrelation } from 'quidproquo-webserv
|
|
|
3
3
|
import { QpqRuntimeDefinition } from '../../hooks/asmj/createQpqRuntimeDefinition';
|
|
4
4
|
import { QpqApi, QpqMappedApi } from '../../hooks/asmj/QpqMappedApi';
|
|
5
5
|
import { ActionProcessorListResolverFactory } from '../../hooks/asmj/useQpqRuntime';
|
|
6
|
-
export declare function useQpqWebsocketQueueRuntime<TState, TAction, TApi extends QpqApi>(atom: QpqRuntimeDefinition<TState, TAction, TApi>, mainStory?: Story<any, any>, name?: string, getActionProcessors?: ActionProcessorListResolverFactory<TState>): [
|
|
7
|
-
QpqMappedApi<TApi>,
|
|
8
|
-
TState,
|
|
9
|
-
(action: any) => void,
|
|
10
|
-
(event: Omit<AnyWebSocketQueueEventMessageWithCorrelation, 'correlationId'>) => Promise<any>
|
|
11
|
-
];
|
|
6
|
+
export declare function useQpqWebsocketQueueRuntime<TState, TAction, TApi extends QpqApi>(atom: QpqRuntimeDefinition<TState, TAction, TApi>, mainStory?: Story<any, any>, name?: string, getActionProcessors?: ActionProcessorListResolverFactory<TState>): [QpqMappedApi<TApi>, TState, (action: any) => void, (event: Omit<AnyWebSocketQueueEventMessageWithCorrelation, 'correlationId'>) => Promise<any>];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { WebsocketServiceEvent } from 'quidproquo-web';
|
|
2
|
-
import { WebSocketQueueServerMessageEventType } from 'quidproquo-webserver';
|
|
2
|
+
import { WebSocketQueueServerMessageEventType, } from 'quidproquo-webserver';
|
|
3
3
|
import { useRef } from 'react';
|
|
4
4
|
import { useFastCallback } from '../../hooks';
|
|
5
5
|
import { useSubscribeToWebsocket, useWebsocketApi } from '../../websocket';
|
|
@@ -2,10 +2,10 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { memo } from 'react';
|
|
3
3
|
import { useWebsocketAuthSync, useWebsocketPingPong } from './hooks';
|
|
4
4
|
import { WebSocketAuthContext } from './WebSocketAuthContext';
|
|
5
|
-
const
|
|
5
|
+
const WebSocketAuthSyncComponent = ({ children, accessToken }) => {
|
|
6
6
|
const isAuthenticated = useWebsocketAuthSync(accessToken);
|
|
7
7
|
// Keep the active websocket alive
|
|
8
8
|
useWebsocketPingPong();
|
|
9
|
-
return
|
|
9
|
+
return _jsx(WebSocketAuthContext.Provider, { value: isAuthenticated, children: children });
|
|
10
10
|
};
|
|
11
|
-
export const WebSocketAuthSync = memo(
|
|
11
|
+
export const WebSocketAuthSync = memo(WebSocketAuthSyncComponent);
|
|
@@ -31,6 +31,6 @@ export const useWebsocketAuthSync = (accessToken) => {
|
|
|
31
31
|
useSubscribeToWebsocket(WebsocketServiceEvent.OPEN, updateAuthTokens);
|
|
32
32
|
useSubscribeToWebsocket(WebsocketServiceEvent.CLOSE, () => setIsAuthenticated(false));
|
|
33
33
|
// Sync the tokens when they change
|
|
34
|
-
useEffect(updateAuthTokens, [accessToken, websocketApi, isAuthenticated]);
|
|
34
|
+
useEffect(updateAuthTokens, [updateAuthTokens, accessToken, websocketApi, isAuthenticated]);
|
|
35
35
|
return isAuthenticated;
|
|
36
36
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quidproquo-web-react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./lib/commonjs/index.js",
|
|
6
6
|
"module": "./lib/esm/index.js",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@testing-library/dom": "^10.4.1",
|
|
36
36
|
"@testing-library/react": "^16.3.2",
|
|
37
|
-
"quidproquo-core": "0.1.
|
|
38
|
-
"quidproquo-tsconfig": "0.1.
|
|
37
|
+
"quidproquo-core": "0.1.2",
|
|
38
|
+
"quidproquo-tsconfig": "0.1.2",
|
|
39
39
|
"typescript": "^5.8.2"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
@@ -43,10 +43,10 @@
|
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"jotai": "^2.12.1",
|
|
46
|
-
"quidproquo-actionprocessor-node": "0.1.
|
|
47
|
-
"quidproquo-actionprocessor-web": "0.1.
|
|
48
|
-
"quidproquo-web": "0.1.
|
|
49
|
-
"quidproquo-webserver": "0.1.
|
|
46
|
+
"quidproquo-actionprocessor-node": "0.1.2",
|
|
47
|
+
"quidproquo-actionprocessor-web": "0.1.2",
|
|
48
|
+
"quidproquo-web": "0.1.2",
|
|
49
|
+
"quidproquo-webserver": "0.1.2",
|
|
50
50
|
"symbol-observable": "^4.0.0"
|
|
51
51
|
}
|
|
52
|
-
}
|
|
52
|
+
}
|