quidproquo-web-react 0.0.262 → 0.1.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/lib/commonjs/actionProcessor/ActionProcessorContext.d.ts +2 -0
- package/lib/commonjs/actionProcessor/ActionProcessorContext.js +14 -0
- package/lib/commonjs/actionProcessor/ActionProcessorProvider.d.ts +6 -0
- package/lib/commonjs/actionProcessor/ActionProcessorProvider.js +24 -0
- package/lib/commonjs/actionProcessor/index.d.ts +3 -0
- package/lib/commonjs/actionProcessor/index.js +19 -0
- package/lib/commonjs/actionProcessor/useActionProcessors.d.ts +1 -0
- package/lib/commonjs/actionProcessor/useActionProcessors.js +7 -0
- package/lib/commonjs/api/hooks/index.d.ts +1 -0
- package/lib/commonjs/api/hooks/index.js +1 -0
- package/lib/commonjs/api/hooks/useApiRequestActionProcessor.d.ts +5 -0
- package/lib/commonjs/api/hooks/useApiRequestActionProcessor.js +22 -0
- package/lib/commonjs/api/hooks/useAuthenticatedNetworkRequest.js +2 -2
- package/lib/commonjs/api/hooks/useNetworkRequest.js +2 -2
- package/lib/commonjs/auth/hooks/useRefreshTokens.js +11 -3
- package/lib/commonjs/dateTime/DateTime.d.ts +28 -0
- package/lib/commonjs/dateTime/DateTime.js +57 -0
- package/lib/commonjs/dateTime/hooks/index.d.ts +1 -0
- package/lib/commonjs/dateTime/hooks/index.js +17 -0
- package/lib/commonjs/dateTime/hooks/useTimeAgo.d.ts +6 -0
- package/lib/commonjs/dateTime/hooks/useTimeAgo.js +26 -0
- package/lib/commonjs/dateTime/index.d.ts +2 -0
- package/lib/commonjs/dateTime/index.js +18 -0
- package/lib/commonjs/dateTime/logic/formatTimeAgo.d.ts +11 -0
- package/lib/commonjs/dateTime/logic/formatTimeAgo.js +49 -0
- package/lib/commonjs/dateTime/logic/index.d.ts +1 -0
- package/lib/commonjs/dateTime/logic/index.js +17 -0
- package/lib/commonjs/hooks/asmj/QpqContextProvider.d.ts +2 -1
- package/lib/commonjs/hooks/asmj/QpqContextProvider.js +6 -1
- package/lib/commonjs/hooks/asmj/QpqRuntimeEffectCatcher.d.ts +2 -2
- package/lib/commonjs/hooks/asmj/index.d.ts +1 -0
- package/lib/commonjs/hooks/asmj/index.js +1 -0
- package/lib/commonjs/hooks/asmj/useBubbleReducer.d.ts +2 -0
- package/lib/commonjs/hooks/asmj/useBubbleReducer.js +22 -0
- package/lib/commonjs/hooks/asmj/useQpqRuntime.d.ts +4 -2
- package/lib/commonjs/hooks/asmj/useQpqRuntime.js +14 -2
- package/lib/commonjs/hooks/index.d.ts +1 -0
- package/lib/commonjs/hooks/index.js +1 -0
- package/lib/commonjs/hooks/useComputed.d.ts +1 -0
- package/lib/commonjs/hooks/useComputed.js +7 -0
- package/lib/commonjs/hooks/useQpq.js +3 -1
- package/lib/commonjs/index.d.ts +2 -0
- package/lib/commonjs/index.js +2 -0
- package/lib/commonjs/webSocketQueue/hooks/actionProcessor/getServiceRequestActionProcessor.d.ts +4 -0
- package/lib/commonjs/webSocketQueue/hooks/actionProcessor/getServiceRequestActionProcessor.js +25 -0
- package/lib/commonjs/webSocketQueue/hooks/index.d.ts +2 -0
- package/lib/commonjs/webSocketQueue/hooks/index.js +2 -0
- package/lib/commonjs/webSocketQueue/hooks/useQpqWebsocketQueueRuntime.d.ts +11 -0
- package/lib/commonjs/webSocketQueue/hooks/useQpqWebsocketQueueRuntime.js +35 -0
- package/lib/commonjs/webSocketQueue/hooks/useWebsocketQueueSendEvent.d.ts +1 -1
- package/lib/commonjs/webSocketQueue/hooks/useWebsocketQueueSendEvent.js +53 -3
- package/lib/commonjs/websocket/WebSocketAuthSync/WebSocketAuthContext.d.ts +1 -0
- package/lib/commonjs/websocket/WebSocketAuthSync/WebSocketAuthContext.js +5 -0
- package/lib/commonjs/websocket/WebSocketAuthSync/WebSocketAuthSync.js +3 -3
- package/lib/commonjs/websocket/WebSocketAuthSync/hooks/index.d.ts +1 -0
- package/lib/commonjs/websocket/WebSocketAuthSync/hooks/index.js +1 -0
- package/lib/commonjs/websocket/WebSocketAuthSync/hooks/useWebSocketIsAuthenticated.d.ts +1 -0
- package/lib/commonjs/websocket/WebSocketAuthSync/hooks/useWebSocketIsAuthenticated.js +9 -0
- package/lib/commonjs/websocket/WebSocketAuthSync/hooks/useWebsocketAuthSync.d.ts +1 -1
- package/lib/commonjs/websocket/WebSocketAuthSync/hooks/useWebsocketAuthSync.js +11 -8
- package/lib/commonjs/websocket/WebSocketAuthSync/index.d.ts +1 -0
- package/lib/commonjs/websocket/WebSocketAuthSync/index.js +1 -0
- package/lib/commonjs/websocket/hooks/useSubscribeToWebSocketEvent.js +4 -2
- package/lib/commonjs/websocket/hooks/useSubscribeToWebsocket.js +4 -2
- package/lib/esm/actionProcessor/ActionProcessorContext.d.ts +2 -0
- package/lib/esm/actionProcessor/ActionProcessorContext.js +2 -0
- package/lib/esm/actionProcessor/ActionProcessorProvider.d.ts +6 -0
- package/lib/esm/actionProcessor/ActionProcessorProvider.js +12 -0
- package/lib/esm/actionProcessor/index.d.ts +3 -0
- package/lib/esm/actionProcessor/index.js +3 -0
- package/lib/esm/actionProcessor/useActionProcessors.d.ts +1 -0
- package/lib/esm/actionProcessor/useActionProcessors.js +3 -0
- package/lib/esm/api/hooks/index.d.ts +1 -0
- package/lib/esm/api/hooks/index.js +1 -0
- package/lib/esm/api/hooks/useApiRequestActionProcessor.d.ts +5 -0
- package/lib/esm/api/hooks/useApiRequestActionProcessor.js +15 -0
- package/lib/esm/api/hooks/useAuthenticatedNetworkRequest.js +1 -1
- package/lib/esm/api/hooks/useNetworkRequest.js +1 -1
- package/lib/esm/auth/hooks/useRefreshTokens.js +11 -3
- package/lib/esm/dateTime/DateTime.d.ts +28 -0
- package/lib/esm/dateTime/DateTime.js +54 -0
- package/lib/esm/dateTime/hooks/index.d.ts +1 -0
- package/lib/esm/dateTime/hooks/index.js +1 -0
- package/lib/esm/dateTime/hooks/useTimeAgo.d.ts +6 -0
- package/lib/esm/dateTime/hooks/useTimeAgo.js +22 -0
- package/lib/esm/dateTime/index.d.ts +2 -0
- package/lib/esm/dateTime/index.js +2 -0
- package/lib/esm/dateTime/logic/formatTimeAgo.d.ts +11 -0
- package/lib/esm/dateTime/logic/formatTimeAgo.js +44 -0
- package/lib/esm/dateTime/logic/index.d.ts +1 -0
- package/lib/esm/dateTime/logic/index.js +1 -0
- package/lib/esm/hooks/asmj/QpqContextProvider.d.ts +2 -1
- package/lib/esm/hooks/asmj/QpqContextProvider.js +4 -0
- package/lib/esm/hooks/asmj/QpqRuntimeEffectCatcher.d.ts +2 -2
- package/lib/esm/hooks/asmj/index.d.ts +1 -0
- package/lib/esm/hooks/asmj/index.js +1 -0
- package/lib/esm/hooks/asmj/useBubbleReducer.d.ts +2 -0
- package/lib/esm/hooks/asmj/useBubbleReducer.js +18 -0
- package/lib/esm/hooks/asmj/useQpqRuntime.d.ts +4 -2
- package/lib/esm/hooks/asmj/useQpqRuntime.js +6 -2
- package/lib/esm/hooks/index.d.ts +1 -0
- package/lib/esm/hooks/index.js +1 -0
- package/lib/esm/hooks/useComputed.d.ts +1 -0
- package/lib/esm/hooks/useComputed.js +4 -0
- package/lib/esm/hooks/useQpq.js +3 -0
- package/lib/esm/index.d.ts +2 -0
- package/lib/esm/index.js +2 -0
- package/lib/esm/webSocketQueue/hooks/actionProcessor/getServiceRequestActionProcessor.d.ts +4 -0
- package/lib/esm/webSocketQueue/hooks/actionProcessor/getServiceRequestActionProcessor.js +11 -0
- package/lib/esm/webSocketQueue/hooks/index.d.ts +2 -0
- package/lib/esm/webSocketQueue/hooks/index.js +2 -0
- package/lib/esm/webSocketQueue/hooks/useQpqWebsocketQueueRuntime.d.ts +11 -0
- package/lib/esm/webSocketQueue/hooks/useQpqWebsocketQueueRuntime.js +24 -0
- package/lib/esm/webSocketQueue/hooks/useWebsocketQueueSendEvent.d.ts +1 -1
- package/lib/esm/webSocketQueue/hooks/useWebsocketQueueSendEvent.js +54 -4
- package/lib/esm/websocket/WebSocketAuthSync/WebSocketAuthContext.d.ts +1 -0
- package/lib/esm/websocket/WebSocketAuthSync/WebSocketAuthContext.js +2 -0
- package/lib/esm/websocket/WebSocketAuthSync/WebSocketAuthSync.js +4 -4
- package/lib/esm/websocket/WebSocketAuthSync/hooks/index.d.ts +1 -0
- package/lib/esm/websocket/WebSocketAuthSync/hooks/index.js +1 -0
- package/lib/esm/websocket/WebSocketAuthSync/hooks/useWebSocketIsAuthenticated.d.ts +1 -0
- package/lib/esm/websocket/WebSocketAuthSync/hooks/useWebSocketIsAuthenticated.js +5 -0
- package/lib/esm/websocket/WebSocketAuthSync/hooks/useWebsocketAuthSync.d.ts +1 -1
- package/lib/esm/websocket/WebSocketAuthSync/hooks/useWebsocketAuthSync.js +14 -11
- package/lib/esm/websocket/WebSocketAuthSync/index.d.ts +1 -0
- package/lib/esm/websocket/WebSocketAuthSync/index.js +1 -0
- package/lib/esm/websocket/hooks/useSubscribeToWebSocketEvent.js +4 -2
- package/lib/esm/websocket/hooks/useSubscribeToWebsocket.js +4 -2
- package/package.json +11 -9
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { formatTimeAgo, getTimeAgoUpdateIntervalMs } from 'quidproquo-web';
|
|
2
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Returns a relative time phrase for `date` (e.g. "5 minutes ago") that
|
|
5
|
+
* updates itself over time. The re-render cadence adapts to the age of the
|
|
6
|
+
* date, so recent times tick every second and older ones only update hourly.
|
|
7
|
+
*/
|
|
8
|
+
export const useTimeAgo = (date, locale) => {
|
|
9
|
+
const [now, setNow] = useState(() => new Date());
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
let timeoutId;
|
|
12
|
+
const schedule = () => {
|
|
13
|
+
timeoutId = setTimeout(() => {
|
|
14
|
+
setNow(new Date());
|
|
15
|
+
schedule();
|
|
16
|
+
}, getTimeAgoUpdateIntervalMs(date, new Date()));
|
|
17
|
+
};
|
|
18
|
+
schedule();
|
|
19
|
+
return () => clearTimeout(timeoutId);
|
|
20
|
+
}, [date]);
|
|
21
|
+
return useMemo(() => formatTimeAgo(date, now, locale), [date, now, locale]);
|
|
22
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats the distance between `date` and `now` as a relative phrase
|
|
3
|
+
* (e.g. "5 minutes ago", "in 2 hours") using the native Intl.RelativeTimeFormat.
|
|
4
|
+
* Past dates are negative, future dates are positive.
|
|
5
|
+
*/
|
|
6
|
+
export declare const formatTimeAgo: (date: Date, now?: Date, locale?: string | string[]) => string;
|
|
7
|
+
/**
|
|
8
|
+
* How long until the relative phrase could next change, so callers can
|
|
9
|
+
* re-render no more often than necessary.
|
|
10
|
+
*/
|
|
11
|
+
export declare const getTimeAgoUpdateIntervalMs: (date: Date, now?: Date) => number;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Each amount is "how many of this unit make up one of the next unit".
|
|
2
|
+
const DIVISIONS = [
|
|
3
|
+
{ amount: 60, unit: 'seconds' },
|
|
4
|
+
{ amount: 60, unit: 'minutes' },
|
|
5
|
+
{ amount: 24, unit: 'hours' },
|
|
6
|
+
{ amount: 7, unit: 'days' },
|
|
7
|
+
{ amount: 4.34524, unit: 'weeks' },
|
|
8
|
+
{ amount: 12, unit: 'months' },
|
|
9
|
+
{ amount: Number.POSITIVE_INFINITY, unit: 'years' },
|
|
10
|
+
];
|
|
11
|
+
/**
|
|
12
|
+
* Formats the distance between `date` and `now` as a relative phrase
|
|
13
|
+
* (e.g. "5 minutes ago", "in 2 hours") using the native Intl.RelativeTimeFormat.
|
|
14
|
+
* Past dates are negative, future dates are positive.
|
|
15
|
+
*/
|
|
16
|
+
export const formatTimeAgo = (date, now = new Date(), locale) => {
|
|
17
|
+
const formatter = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
|
|
18
|
+
// Seconds between the two dates; negative for the past.
|
|
19
|
+
let duration = (date.getTime() - now.getTime()) / 1000;
|
|
20
|
+
for (const division of DIVISIONS) {
|
|
21
|
+
if (Math.abs(duration) < division.amount) {
|
|
22
|
+
return formatter.format(Math.round(duration), division.unit);
|
|
23
|
+
}
|
|
24
|
+
duration /= division.amount;
|
|
25
|
+
}
|
|
26
|
+
return formatter.format(Math.round(duration), 'years');
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* How long until the relative phrase could next change, so callers can
|
|
30
|
+
* re-render no more often than necessary.
|
|
31
|
+
*/
|
|
32
|
+
export const getTimeAgoUpdateIntervalMs = (date, now = new Date()) => {
|
|
33
|
+
const seconds = Math.abs((date.getTime() - now.getTime()) / 1000);
|
|
34
|
+
if (seconds < 60) {
|
|
35
|
+
return 1000; // seconds tick over
|
|
36
|
+
}
|
|
37
|
+
if (seconds < 60 * 60) {
|
|
38
|
+
return 60 * 1000; // minutes tick over
|
|
39
|
+
}
|
|
40
|
+
if (seconds < 60 * 60 * 24) {
|
|
41
|
+
return 60 * 60 * 1000; // hours tick over
|
|
42
|
+
}
|
|
43
|
+
return 60 * 60 * 24 * 1000; // days or coarser
|
|
44
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './formatTimeAgo';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './formatTimeAgo';
|
|
@@ -6,5 +6,6 @@ interface QpqContextProviderProps<T> {
|
|
|
6
6
|
children: ReactNode;
|
|
7
7
|
}
|
|
8
8
|
export declare const useQpqContextValues: () => QpqContext<any>;
|
|
9
|
-
export declare const
|
|
9
|
+
export declare const useQpqContextValue: <T>(contextIdentifier: QpqContextIdentifier<T>) => T;
|
|
10
|
+
export declare const QpqContextProvider: <T>({ contextIdentifier, value, children }: QpqContextProviderProps<T>) => import("react").JSX.Element;
|
|
10
11
|
export {};
|
|
@@ -5,6 +5,10 @@ const qpqReactContext = createContext({});
|
|
|
5
5
|
export const useQpqContextValues = () => {
|
|
6
6
|
return useContext(qpqReactContext);
|
|
7
7
|
};
|
|
8
|
+
export const useQpqContextValue = (contextIdentifier) => {
|
|
9
|
+
const context = useQpqContextValues();
|
|
10
|
+
return contextIdentifier.uniqueName in context ? context[contextIdentifier.uniqueName] : contextIdentifier.defaultValue;
|
|
11
|
+
};
|
|
8
12
|
export const QpqContextProvider = ({ contextIdentifier, value, children }) => {
|
|
9
13
|
// Get the parent context value
|
|
10
14
|
const parentContext = useContext(qpqReactContext);
|
|
@@ -7,8 +7,8 @@ type QpqRuntimeEffectCatcher = {
|
|
|
7
7
|
};
|
|
8
8
|
export declare const QpqRuntimeEffectCatcherComponent: <TState, TAction, TApi extends QpqApi>({ children, runtime, name, }: QpqRuntimeEffectCatcher & {
|
|
9
9
|
children: ReactNode;
|
|
10
|
-
}) => import("react
|
|
10
|
+
}) => import("react").JSX.Element;
|
|
11
11
|
export declare const QpqRuntimeEffectCatcher: import("react").MemoExoticComponent<(<TState, TAction, TApi extends QpqApi>({ children, runtime, name, }: QpqRuntimeEffectCatcher & {
|
|
12
12
|
children: ReactNode;
|
|
13
|
-
}) => import("react
|
|
13
|
+
}) => import("react").JSX.Element)>;
|
|
14
14
|
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useCallback, useContext, useRef, useState } from 'react';
|
|
2
|
+
import { BubbleReducerDispatchContext } from './bubbleReducer';
|
|
3
|
+
export const useBubbleReducer = (reducer, initialState) => {
|
|
4
|
+
const parentDispatch = useContext(BubbleReducerDispatchContext);
|
|
5
|
+
const [state, setState] = useState(initialState);
|
|
6
|
+
const stateRef = useRef(state);
|
|
7
|
+
stateRef.current = state;
|
|
8
|
+
const dispatch = useCallback((action) => {
|
|
9
|
+
const [newState, preventBubble] = reducer(stateRef.current, action);
|
|
10
|
+
if (preventBubble) {
|
|
11
|
+
setState(newState);
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
parentDispatch(action);
|
|
15
|
+
}
|
|
16
|
+
}, [parentDispatch, reducer]);
|
|
17
|
+
return [state, dispatch];
|
|
18
|
+
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { Story } from 'quidproquo-core';
|
|
1
|
+
import { ActionProcessorListResolver, Story } from 'quidproquo-core';
|
|
2
|
+
import { Dispatch } from 'react';
|
|
2
3
|
import { QpqRuntimeDefinition } from './createQpqRuntimeDefinition';
|
|
3
4
|
import { QpqApi, QpqMappedApi } from './QpqMappedApi';
|
|
4
|
-
export
|
|
5
|
+
export type ActionProcessorListResolverFactory<TState = any> = (dispatch: Dispatch<any>, getCurrentState: () => TState) => ActionProcessorListResolver;
|
|
6
|
+
export declare function useQpqRuntime<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];
|
|
@@ -2,12 +2,16 @@ import { useEffect, useMemo, useState } from 'react';
|
|
|
2
2
|
import { useQpq } from '../useQpq';
|
|
3
3
|
import { getStateActionProcessor } from './actionProcessor';
|
|
4
4
|
import { useQpqRuntimeBubblingReducer } from './bubbleReducer';
|
|
5
|
-
export function useQpqRuntime(atom, mainStory, name) {
|
|
5
|
+
export function useQpqRuntime(atom, mainStory, name, getActionProcessors = () => async () => ({})) {
|
|
6
6
|
const atomInfo = useMemo(() => atom(name), [atom, name]);
|
|
7
7
|
const [state, dispatch, getCurrentState] = useQpqRuntimeBubblingReducer(atom, name);
|
|
8
8
|
// Api generators are memoized to prevent unnecessary re-renders.
|
|
9
9
|
const [memoedApiGenerators] = useState(() => atomInfo.api);
|
|
10
|
-
const
|
|
10
|
+
const mergedProcessors = async (qpqConfig, dynamicModuleLoader) => ({
|
|
11
|
+
...(await getStateActionProcessor(dispatch, getCurrentState)(qpqConfig, dynamicModuleLoader)),
|
|
12
|
+
...(await getActionProcessors(dispatch, getCurrentState)(qpqConfig, dynamicModuleLoader)),
|
|
13
|
+
});
|
|
14
|
+
const resolver = useQpq(mergedProcessors);
|
|
11
15
|
// Wrap and remap each API generator using the resolver.
|
|
12
16
|
const api = useMemo(() => {
|
|
13
17
|
const wrapped = {};
|
package/lib/esm/hooks/index.d.ts
CHANGED
package/lib/esm/hooks/index.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useComputed<TState extends object, T>(state: TState, selector: (state: TState) => T): T;
|
package/lib/esm/hooks/useQpq.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getWebActionProcessors } from 'quidproquo-actionprocessor-web';
|
|
2
2
|
import { createRuntime, defineModule, QpqRuntimeType, } from 'quidproquo-core';
|
|
3
3
|
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
4
|
+
import { useActionProcessors } from '../actionProcessor';
|
|
4
5
|
import { useQpqContextValues } from './asmj/QpqContextProvider';
|
|
5
6
|
function* withVersionCheck(story, versionRef, capturedVersion) {
|
|
6
7
|
let nextValue = undefined;
|
|
@@ -25,6 +26,7 @@ const logger = {
|
|
|
25
26
|
};
|
|
26
27
|
export function useQpq(getActionProcessors = async () => ({})) {
|
|
27
28
|
const qpqContextValues = useQpqContextValues();
|
|
29
|
+
const contextGetActionProcessors = useActionProcessors();
|
|
28
30
|
const versionRef = useRef(0);
|
|
29
31
|
useEffect(() => {
|
|
30
32
|
return () => {
|
|
@@ -36,6 +38,7 @@ export function useQpq(getActionProcessors = async () => ({})) {
|
|
|
36
38
|
context: qpqContextValues,
|
|
37
39
|
}, async (qpqConfig, dynamicModuleLoader) => ({
|
|
38
40
|
...(await getWebActionProcessors(qpqConfig, dynamicModuleLoader)),
|
|
41
|
+
...(await contextGetActionProcessors(qpqConfig, dynamicModuleLoader)),
|
|
39
42
|
...(await getActionProcessors(qpqConfig, dynamicModuleLoader)),
|
|
40
43
|
}), () => new Date().toISOString(), logger, `frontend::${'uuid'}`, QpqRuntimeType.UI, async (_runtime) => {
|
|
41
44
|
// noop
|
package/lib/esm/index.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import 'symbol-observable';
|
|
2
|
+
export * from './actionProcessor';
|
|
2
3
|
export * from './api';
|
|
3
4
|
export * from './auth';
|
|
4
5
|
export * from './baseUrl';
|
|
6
|
+
export * from './dateTime';
|
|
5
7
|
export * from './hooks';
|
|
6
8
|
export * from './state';
|
|
7
9
|
export * from './useFieldBinding';
|
package/lib/esm/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import 'symbol-observable';
|
|
2
|
+
export * from './actionProcessor';
|
|
2
3
|
export * from './api';
|
|
3
4
|
export * from './auth';
|
|
4
5
|
export * from './baseUrl';
|
|
6
|
+
export * from './dateTime';
|
|
5
7
|
export * from './hooks';
|
|
6
8
|
export * from './state';
|
|
7
9
|
export * from './useFieldBinding';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { ActionProcessorList } from 'quidproquo-core';
|
|
2
|
+
import { AnyWebSocketQueueEventMessageWithCorrelation } from 'quidproquo-webserver';
|
|
3
|
+
import { RefObject } from 'react';
|
|
4
|
+
export declare const getServiceRequestActionProcessor: (sendEventRef: RefObject<((event: Omit<AnyWebSocketQueueEventMessageWithCorrelation, "correlationId">) => Promise<any>) | null>) => ActionProcessorList;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { actionResult, actionResultError } from 'quidproquo-core';
|
|
2
|
+
import { ServiceActionType } from 'quidproquo-webserver';
|
|
3
|
+
export const getServiceRequestActionProcessor = (sendEventRef) => ({
|
|
4
|
+
[ServiceActionType.Request]: async ({ serviceName, method, payload, }) => {
|
|
5
|
+
const response = await sendEventRef.current?.({ type: `qpq/serviceRequest/${serviceName}/${method}`, payload });
|
|
6
|
+
if (response && !response.success) {
|
|
7
|
+
return actionResultError(response.error.errorType, response.error.errorText, response.error.errorStack);
|
|
8
|
+
}
|
|
9
|
+
return actionResult(response?.result);
|
|
10
|
+
},
|
|
11
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Story } from 'quidproquo-core';
|
|
2
|
+
import { AnyWebSocketQueueEventMessageWithCorrelation } from 'quidproquo-webserver';
|
|
3
|
+
import { QpqRuntimeDefinition } from '../../hooks/asmj/createQpqRuntimeDefinition';
|
|
4
|
+
import { QpqApi, QpqMappedApi } from '../../hooks/asmj/QpqMappedApi';
|
|
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
|
+
];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { WebSocketQueueServerMessageEventType } from 'quidproquo-webserver';
|
|
2
|
+
import { useRef } from 'react';
|
|
3
|
+
import { useQpqRuntime } from '../../hooks/asmj/useQpqRuntime';
|
|
4
|
+
import { getServiceRequestActionProcessor } from './actionProcessor/getServiceRequestActionProcessor';
|
|
5
|
+
import { useWebsocketQueueSendEvent } from './useWebsocketQueueSendEvent';
|
|
6
|
+
export function useQpqWebsocketQueueRuntime(atom, mainStory, name, getActionProcessors) {
|
|
7
|
+
const sendEventRef = useRef(null);
|
|
8
|
+
const mergedFactory = (dispatch, getCurrentState) => {
|
|
9
|
+
const serviceResolver = async () => getServiceRequestActionProcessor(sendEventRef);
|
|
10
|
+
const callerResolver = getActionProcessors?.(dispatch, getCurrentState);
|
|
11
|
+
return async (qpqConfig, dynamicModuleLoader) => ({
|
|
12
|
+
...(await serviceResolver()),
|
|
13
|
+
...(callerResolver ? await callerResolver(qpqConfig, dynamicModuleLoader) : {}),
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
const [api, state, dispatch] = useQpqRuntime(atom, mainStory, name, mergedFactory);
|
|
17
|
+
const sendEvent = useWebsocketQueueSendEvent((event) => {
|
|
18
|
+
if (event.type === WebSocketQueueServerMessageEventType.StateDispatch) {
|
|
19
|
+
dispatch(event.payload);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
sendEventRef.current = sendEvent;
|
|
23
|
+
return [api, state, dispatch, sendEvent];
|
|
24
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { AnyWebSocketQueueEventMessageWithCorrelation } from 'quidproquo-webserver';
|
|
2
|
-
export declare const useWebsocketQueueSendEvent: <
|
|
2
|
+
export declare const useWebsocketQueueSendEvent: <TSend extends AnyWebSocketQueueEventMessageWithCorrelation>(onCorrelatedMessage?: (event: AnyWebSocketQueueEventMessageWithCorrelation) => void) => (event: Omit<TSend, "correlationId">) => Promise<any>;
|
|
@@ -1,5 +1,55 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { WebsocketServiceEvent } from 'quidproquo-web';
|
|
2
|
+
import { WebSocketQueueServerMessageEventType } from 'quidproquo-webserver';
|
|
3
|
+
import { useRef } from 'react';
|
|
4
|
+
import { useFastCallback } from '../../hooks';
|
|
5
|
+
import { useSubscribeToWebsocket, useWebsocketApi } from '../../websocket';
|
|
6
|
+
export const useWebsocketQueueSendEvent = (onCorrelatedMessage) => {
|
|
7
|
+
const websocketApi = useWebsocketApi();
|
|
8
|
+
const activeCorrelationsRef = useRef(new Set());
|
|
9
|
+
const pendingResolversRef = useRef(new Map());
|
|
10
|
+
// Subscribe to ALL messages, filter by active correlationIds
|
|
11
|
+
const handleMessage = useFastCallback((_ws, rawEvent) => {
|
|
12
|
+
if (!rawEvent)
|
|
13
|
+
return;
|
|
14
|
+
try {
|
|
15
|
+
const parsed = JSON.parse(rawEvent.data);
|
|
16
|
+
if (!parsed.correlationId || !activeCorrelationsRef.current.has(parsed.correlationId)) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (parsed.type === WebSocketQueueServerMessageEventType.ServiceRequestResponse) {
|
|
20
|
+
const resolve = pendingResolversRef.current.get(parsed.correlationId);
|
|
21
|
+
if (resolve) {
|
|
22
|
+
resolve(parsed.payload);
|
|
23
|
+
pendingResolversRef.current.delete(parsed.correlationId);
|
|
24
|
+
activeCorrelationsRef.current.delete(parsed.correlationId);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
onCorrelatedMessage?.(parsed);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Not JSON or not a queue message — ignore
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
useSubscribeToWebsocket(WebsocketServiceEvent.MESSAGE, handleMessage);
|
|
36
|
+
// Send with auto-generated correlationId, register it as active
|
|
37
|
+
const sendEvent = useFastCallback((event) => {
|
|
38
|
+
if (!websocketApi) {
|
|
39
|
+
return Promise.resolve(undefined);
|
|
40
|
+
}
|
|
41
|
+
const correlationId = crypto.randomUUID();
|
|
42
|
+
activeCorrelationsRef.current.add(correlationId);
|
|
43
|
+
const isServiceRequest = event.type.startsWith('qpq/serviceRequest/');
|
|
44
|
+
if (isServiceRequest) {
|
|
45
|
+
const promise = new Promise((resolve) => {
|
|
46
|
+
pendingResolversRef.current.set(correlationId, resolve);
|
|
47
|
+
});
|
|
48
|
+
websocketApi.sendEvent({ ...event, correlationId });
|
|
49
|
+
return promise;
|
|
50
|
+
}
|
|
51
|
+
websocketApi.sendEvent({ ...event, correlationId });
|
|
52
|
+
return Promise.resolve(undefined);
|
|
53
|
+
});
|
|
54
|
+
return sendEvent;
|
|
5
55
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const WebSocketAuthContext: import("react").Context<boolean>;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { memo } from 'react';
|
|
3
3
|
import { useWebsocketAuthSync, useWebsocketPingPong } from './hooks';
|
|
4
|
+
import { WebSocketAuthContext } from './WebSocketAuthContext';
|
|
4
5
|
const component = ({ children, accessToken }) => {
|
|
5
|
-
useWebsocketAuthSync(accessToken);
|
|
6
|
+
const isAuthenticated = useWebsocketAuthSync(accessToken);
|
|
6
7
|
// Keep the active websocket alive
|
|
7
8
|
useWebsocketPingPong();
|
|
8
|
-
|
|
9
|
-
return _jsx(_Fragment, { children: children });
|
|
9
|
+
return (_jsx(WebSocketAuthContext.Provider, { value: isAuthenticated, children: children }));
|
|
10
10
|
};
|
|
11
11
|
export const WebSocketAuthSync = memo(component);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const useWebSocketIsAuthenticated: () => boolean;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { AuthenticationInfo } from 'quidproquo-core';
|
|
2
|
-
export declare const useWebsocketAuthSync: (accessToken: AuthenticationInfo["accessToken"]) =>
|
|
2
|
+
export declare const useWebsocketAuthSync: (accessToken: AuthenticationInfo["accessToken"]) => boolean;
|
|
@@ -1,33 +1,36 @@
|
|
|
1
1
|
import { WebsocketServiceEvent } from 'quidproquo-web';
|
|
2
|
-
import { WebSocketQueueClientMessageEventType, } from 'quidproquo-webserver';
|
|
3
|
-
import { useEffect } from 'react';
|
|
2
|
+
import { WebSocketQueueClientMessageEventType, WebSocketQueueServerMessageEventType, } from 'quidproquo-webserver';
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
4
|
import { useFastCallback } from '../../../hooks';
|
|
5
|
-
import { useSubscribeToWebsocket, useWebsocketApi, useWebsocketSendEvent } from '../../hooks';
|
|
5
|
+
import { useSubscribeToWebsocket, useSubscribeToWebSocketEvent, useWebsocketApi, useWebsocketSendEvent } from '../../hooks';
|
|
6
6
|
export const useWebsocketAuthSync = (accessToken) => {
|
|
7
7
|
const sendMessage = useWebsocketSendEvent();
|
|
8
8
|
const websocketApi = useWebsocketApi();
|
|
9
|
+
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
9
10
|
const updateAuthTokens = useFastCallback(() => {
|
|
10
11
|
if (!websocketApi?.isConnected()) {
|
|
11
12
|
return;
|
|
12
13
|
}
|
|
13
14
|
if (accessToken) {
|
|
14
|
-
|
|
15
|
+
sendMessage({
|
|
15
16
|
type: WebSocketQueueClientMessageEventType.Authenticate,
|
|
16
17
|
payload: {
|
|
17
18
|
accessToken: accessToken,
|
|
18
19
|
},
|
|
19
|
-
};
|
|
20
|
-
sendMessage(authMessage);
|
|
20
|
+
});
|
|
21
21
|
}
|
|
22
|
-
else {
|
|
23
|
-
|
|
22
|
+
else if (isAuthenticated) {
|
|
23
|
+
sendMessage({
|
|
24
24
|
type: WebSocketQueueClientMessageEventType.Unauthenticate,
|
|
25
|
-
};
|
|
26
|
-
sendMessage(authMessage);
|
|
25
|
+
});
|
|
27
26
|
}
|
|
28
27
|
});
|
|
28
|
+
useSubscribeToWebSocketEvent(WebSocketQueueServerMessageEventType.Authenticated, () => setIsAuthenticated(true));
|
|
29
|
+
useSubscribeToWebSocketEvent(WebSocketQueueServerMessageEventType.Unauthenticated, () => setIsAuthenticated(false));
|
|
29
30
|
// Sync the tokens in on open
|
|
30
31
|
useSubscribeToWebsocket(WebsocketServiceEvent.OPEN, updateAuthTokens);
|
|
32
|
+
useSubscribeToWebsocket(WebsocketServiceEvent.CLOSE, () => setIsAuthenticated(false));
|
|
31
33
|
// Sync the tokens when they change
|
|
32
|
-
useEffect(updateAuthTokens, [accessToken, websocketApi]);
|
|
34
|
+
useEffect(updateAuthTokens, [accessToken, websocketApi, isAuthenticated]);
|
|
35
|
+
return isAuthenticated;
|
|
33
36
|
};
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
|
+
import { useFastCallback } from '../../hooks';
|
|
2
3
|
import { useWebsocketApi } from './useWebsocketApi';
|
|
3
4
|
export const useSubscribeToWebSocketEvent = (subscriptionType, callback) => {
|
|
5
|
+
const stableCallback = useFastCallback(callback);
|
|
4
6
|
const websocketService = useWebsocketApi();
|
|
5
7
|
useEffect(() => {
|
|
6
|
-
const subscriptionHandle = websocketService?.subscribeToEvent(subscriptionType,
|
|
8
|
+
const subscriptionHandle = websocketService?.subscribeToEvent(subscriptionType, stableCallback);
|
|
7
9
|
return () => {
|
|
8
10
|
if (subscriptionHandle) {
|
|
9
11
|
websocketService?.unsubscribe(subscriptionHandle);
|
|
10
12
|
}
|
|
11
13
|
};
|
|
12
|
-
}, [websocketService, subscriptionType,
|
|
14
|
+
}, [websocketService, subscriptionType, stableCallback]);
|
|
13
15
|
};
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
|
+
import { useFastCallback } from '../../hooks';
|
|
2
3
|
import { useWebsocketApi } from './useWebsocketApi';
|
|
3
4
|
export const useSubscribeToWebsocket = (subscriptionType, callback) => {
|
|
5
|
+
const stableCallback = useFastCallback(callback);
|
|
4
6
|
const websocketService = useWebsocketApi();
|
|
5
7
|
useEffect(() => {
|
|
6
|
-
const handle = websocketService?.subscribe(subscriptionType,
|
|
8
|
+
const handle = websocketService?.subscribe(subscriptionType, stableCallback);
|
|
7
9
|
return () => {
|
|
8
10
|
if (handle) {
|
|
9
11
|
websocketService?.unsubscribe(handle);
|
|
10
12
|
}
|
|
11
13
|
};
|
|
12
|
-
}, [websocketService, subscriptionType,
|
|
14
|
+
}, [websocketService, subscriptionType, stableCallback]);
|
|
13
15
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quidproquo-web-react",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./lib/commonjs/index.js",
|
|
6
6
|
"module": "./lib/esm/index.js",
|
|
@@ -32,8 +32,10 @@
|
|
|
32
32
|
},
|
|
33
33
|
"homepage": "https://github.com/joe-coady/quidproquo#readme",
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"
|
|
36
|
-
"
|
|
35
|
+
"@testing-library/dom": "^10.4.1",
|
|
36
|
+
"@testing-library/react": "^16.3.2",
|
|
37
|
+
"quidproquo-core": "0.1.0",
|
|
38
|
+
"quidproquo-tsconfig": "0.1.0",
|
|
37
39
|
"typescript": "^5.8.2"
|
|
38
40
|
},
|
|
39
41
|
"peerDependencies": {
|
|
@@ -41,10 +43,10 @@
|
|
|
41
43
|
},
|
|
42
44
|
"dependencies": {
|
|
43
45
|
"jotai": "^2.12.1",
|
|
44
|
-
"
|
|
45
|
-
"quidproquo-web": "0.0
|
|
46
|
-
"quidproquo-
|
|
47
|
-
"quidproquo-
|
|
48
|
-
"
|
|
46
|
+
"quidproquo-actionprocessor-node": "0.1.0",
|
|
47
|
+
"quidproquo-actionprocessor-web": "0.1.0",
|
|
48
|
+
"quidproquo-web": "0.1.0",
|
|
49
|
+
"quidproquo-webserver": "0.1.0",
|
|
50
|
+
"symbol-observable": "^4.0.0"
|
|
49
51
|
}
|
|
50
|
-
}
|
|
52
|
+
}
|