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.
Files changed (129) hide show
  1. package/lib/commonjs/actionProcessor/ActionProcessorContext.d.ts +2 -0
  2. package/lib/commonjs/actionProcessor/ActionProcessorContext.js +14 -0
  3. package/lib/commonjs/actionProcessor/ActionProcessorProvider.d.ts +6 -0
  4. package/lib/commonjs/actionProcessor/ActionProcessorProvider.js +24 -0
  5. package/lib/commonjs/actionProcessor/index.d.ts +3 -0
  6. package/lib/commonjs/actionProcessor/index.js +19 -0
  7. package/lib/commonjs/actionProcessor/useActionProcessors.d.ts +1 -0
  8. package/lib/commonjs/actionProcessor/useActionProcessors.js +7 -0
  9. package/lib/commonjs/api/hooks/index.d.ts +1 -0
  10. package/lib/commonjs/api/hooks/index.js +1 -0
  11. package/lib/commonjs/api/hooks/useApiRequestActionProcessor.d.ts +5 -0
  12. package/lib/commonjs/api/hooks/useApiRequestActionProcessor.js +22 -0
  13. package/lib/commonjs/api/hooks/useAuthenticatedNetworkRequest.js +2 -2
  14. package/lib/commonjs/api/hooks/useNetworkRequest.js +2 -2
  15. package/lib/commonjs/auth/hooks/useRefreshTokens.js +11 -3
  16. package/lib/commonjs/dateTime/DateTime.d.ts +28 -0
  17. package/lib/commonjs/dateTime/DateTime.js +57 -0
  18. package/lib/commonjs/dateTime/hooks/index.d.ts +1 -0
  19. package/lib/commonjs/dateTime/hooks/index.js +17 -0
  20. package/lib/commonjs/dateTime/hooks/useTimeAgo.d.ts +6 -0
  21. package/lib/commonjs/dateTime/hooks/useTimeAgo.js +26 -0
  22. package/lib/commonjs/dateTime/index.d.ts +2 -0
  23. package/lib/commonjs/dateTime/index.js +18 -0
  24. package/lib/commonjs/dateTime/logic/formatTimeAgo.d.ts +11 -0
  25. package/lib/commonjs/dateTime/logic/formatTimeAgo.js +49 -0
  26. package/lib/commonjs/dateTime/logic/index.d.ts +1 -0
  27. package/lib/commonjs/dateTime/logic/index.js +17 -0
  28. package/lib/commonjs/hooks/asmj/QpqContextProvider.d.ts +2 -1
  29. package/lib/commonjs/hooks/asmj/QpqContextProvider.js +6 -1
  30. package/lib/commonjs/hooks/asmj/QpqRuntimeEffectCatcher.d.ts +2 -2
  31. package/lib/commonjs/hooks/asmj/index.d.ts +1 -0
  32. package/lib/commonjs/hooks/asmj/index.js +1 -0
  33. package/lib/commonjs/hooks/asmj/useBubbleReducer.d.ts +2 -0
  34. package/lib/commonjs/hooks/asmj/useBubbleReducer.js +22 -0
  35. package/lib/commonjs/hooks/asmj/useQpqRuntime.d.ts +4 -2
  36. package/lib/commonjs/hooks/asmj/useQpqRuntime.js +14 -2
  37. package/lib/commonjs/hooks/index.d.ts +1 -0
  38. package/lib/commonjs/hooks/index.js +1 -0
  39. package/lib/commonjs/hooks/useComputed.d.ts +1 -0
  40. package/lib/commonjs/hooks/useComputed.js +7 -0
  41. package/lib/commonjs/hooks/useQpq.js +3 -1
  42. package/lib/commonjs/index.d.ts +2 -0
  43. package/lib/commonjs/index.js +2 -0
  44. package/lib/commonjs/webSocketQueue/hooks/actionProcessor/getServiceRequestActionProcessor.d.ts +4 -0
  45. package/lib/commonjs/webSocketQueue/hooks/actionProcessor/getServiceRequestActionProcessor.js +25 -0
  46. package/lib/commonjs/webSocketQueue/hooks/index.d.ts +2 -0
  47. package/lib/commonjs/webSocketQueue/hooks/index.js +2 -0
  48. package/lib/commonjs/webSocketQueue/hooks/useQpqWebsocketQueueRuntime.d.ts +11 -0
  49. package/lib/commonjs/webSocketQueue/hooks/useQpqWebsocketQueueRuntime.js +35 -0
  50. package/lib/commonjs/webSocketQueue/hooks/useWebsocketQueueSendEvent.d.ts +1 -1
  51. package/lib/commonjs/webSocketQueue/hooks/useWebsocketQueueSendEvent.js +53 -3
  52. package/lib/commonjs/websocket/WebSocketAuthSync/WebSocketAuthContext.d.ts +1 -0
  53. package/lib/commonjs/websocket/WebSocketAuthSync/WebSocketAuthContext.js +5 -0
  54. package/lib/commonjs/websocket/WebSocketAuthSync/WebSocketAuthSync.js +3 -3
  55. package/lib/commonjs/websocket/WebSocketAuthSync/hooks/index.d.ts +1 -0
  56. package/lib/commonjs/websocket/WebSocketAuthSync/hooks/index.js +1 -0
  57. package/lib/commonjs/websocket/WebSocketAuthSync/hooks/useWebSocketIsAuthenticated.d.ts +1 -0
  58. package/lib/commonjs/websocket/WebSocketAuthSync/hooks/useWebSocketIsAuthenticated.js +9 -0
  59. package/lib/commonjs/websocket/WebSocketAuthSync/hooks/useWebsocketAuthSync.d.ts +1 -1
  60. package/lib/commonjs/websocket/WebSocketAuthSync/hooks/useWebsocketAuthSync.js +11 -8
  61. package/lib/commonjs/websocket/WebSocketAuthSync/index.d.ts +1 -0
  62. package/lib/commonjs/websocket/WebSocketAuthSync/index.js +1 -0
  63. package/lib/commonjs/websocket/hooks/useSubscribeToWebSocketEvent.js +4 -2
  64. package/lib/commonjs/websocket/hooks/useSubscribeToWebsocket.js +4 -2
  65. package/lib/esm/actionProcessor/ActionProcessorContext.d.ts +2 -0
  66. package/lib/esm/actionProcessor/ActionProcessorContext.js +2 -0
  67. package/lib/esm/actionProcessor/ActionProcessorProvider.d.ts +6 -0
  68. package/lib/esm/actionProcessor/ActionProcessorProvider.js +12 -0
  69. package/lib/esm/actionProcessor/index.d.ts +3 -0
  70. package/lib/esm/actionProcessor/index.js +3 -0
  71. package/lib/esm/actionProcessor/useActionProcessors.d.ts +1 -0
  72. package/lib/esm/actionProcessor/useActionProcessors.js +3 -0
  73. package/lib/esm/api/hooks/index.d.ts +1 -0
  74. package/lib/esm/api/hooks/index.js +1 -0
  75. package/lib/esm/api/hooks/useApiRequestActionProcessor.d.ts +5 -0
  76. package/lib/esm/api/hooks/useApiRequestActionProcessor.js +15 -0
  77. package/lib/esm/api/hooks/useAuthenticatedNetworkRequest.js +1 -1
  78. package/lib/esm/api/hooks/useNetworkRequest.js +1 -1
  79. package/lib/esm/auth/hooks/useRefreshTokens.js +11 -3
  80. package/lib/esm/dateTime/DateTime.d.ts +28 -0
  81. package/lib/esm/dateTime/DateTime.js +54 -0
  82. package/lib/esm/dateTime/hooks/index.d.ts +1 -0
  83. package/lib/esm/dateTime/hooks/index.js +1 -0
  84. package/lib/esm/dateTime/hooks/useTimeAgo.d.ts +6 -0
  85. package/lib/esm/dateTime/hooks/useTimeAgo.js +22 -0
  86. package/lib/esm/dateTime/index.d.ts +2 -0
  87. package/lib/esm/dateTime/index.js +2 -0
  88. package/lib/esm/dateTime/logic/formatTimeAgo.d.ts +11 -0
  89. package/lib/esm/dateTime/logic/formatTimeAgo.js +44 -0
  90. package/lib/esm/dateTime/logic/index.d.ts +1 -0
  91. package/lib/esm/dateTime/logic/index.js +1 -0
  92. package/lib/esm/hooks/asmj/QpqContextProvider.d.ts +2 -1
  93. package/lib/esm/hooks/asmj/QpqContextProvider.js +4 -0
  94. package/lib/esm/hooks/asmj/QpqRuntimeEffectCatcher.d.ts +2 -2
  95. package/lib/esm/hooks/asmj/index.d.ts +1 -0
  96. package/lib/esm/hooks/asmj/index.js +1 -0
  97. package/lib/esm/hooks/asmj/useBubbleReducer.d.ts +2 -0
  98. package/lib/esm/hooks/asmj/useBubbleReducer.js +18 -0
  99. package/lib/esm/hooks/asmj/useQpqRuntime.d.ts +4 -2
  100. package/lib/esm/hooks/asmj/useQpqRuntime.js +6 -2
  101. package/lib/esm/hooks/index.d.ts +1 -0
  102. package/lib/esm/hooks/index.js +1 -0
  103. package/lib/esm/hooks/useComputed.d.ts +1 -0
  104. package/lib/esm/hooks/useComputed.js +4 -0
  105. package/lib/esm/hooks/useQpq.js +3 -0
  106. package/lib/esm/index.d.ts +2 -0
  107. package/lib/esm/index.js +2 -0
  108. package/lib/esm/webSocketQueue/hooks/actionProcessor/getServiceRequestActionProcessor.d.ts +4 -0
  109. package/lib/esm/webSocketQueue/hooks/actionProcessor/getServiceRequestActionProcessor.js +11 -0
  110. package/lib/esm/webSocketQueue/hooks/index.d.ts +2 -0
  111. package/lib/esm/webSocketQueue/hooks/index.js +2 -0
  112. package/lib/esm/webSocketQueue/hooks/useQpqWebsocketQueueRuntime.d.ts +11 -0
  113. package/lib/esm/webSocketQueue/hooks/useQpqWebsocketQueueRuntime.js +24 -0
  114. package/lib/esm/webSocketQueue/hooks/useWebsocketQueueSendEvent.d.ts +1 -1
  115. package/lib/esm/webSocketQueue/hooks/useWebsocketQueueSendEvent.js +54 -4
  116. package/lib/esm/websocket/WebSocketAuthSync/WebSocketAuthContext.d.ts +1 -0
  117. package/lib/esm/websocket/WebSocketAuthSync/WebSocketAuthContext.js +2 -0
  118. package/lib/esm/websocket/WebSocketAuthSync/WebSocketAuthSync.js +4 -4
  119. package/lib/esm/websocket/WebSocketAuthSync/hooks/index.d.ts +1 -0
  120. package/lib/esm/websocket/WebSocketAuthSync/hooks/index.js +1 -0
  121. package/lib/esm/websocket/WebSocketAuthSync/hooks/useWebSocketIsAuthenticated.d.ts +1 -0
  122. package/lib/esm/websocket/WebSocketAuthSync/hooks/useWebSocketIsAuthenticated.js +5 -0
  123. package/lib/esm/websocket/WebSocketAuthSync/hooks/useWebsocketAuthSync.d.ts +1 -1
  124. package/lib/esm/websocket/WebSocketAuthSync/hooks/useWebsocketAuthSync.js +14 -11
  125. package/lib/esm/websocket/WebSocketAuthSync/index.d.ts +1 -0
  126. package/lib/esm/websocket/WebSocketAuthSync/index.js +1 -0
  127. package/lib/esm/websocket/hooks/useSubscribeToWebSocketEvent.js +4 -2
  128. package/lib/esm/websocket/hooks/useSubscribeToWebsocket.js +4 -2
  129. 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,2 @@
1
+ export * from './DateTime';
2
+ export * from './hooks';
@@ -0,0 +1,2 @@
1
+ export * from './DateTime';
2
+ export * from './hooks';
@@ -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 QpqContextProvider: <T>({ contextIdentifier, value, children }: QpqContextProviderProps<T>) => import("react/jsx-runtime").JSX.Element;
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/jsx-runtime").JSX.Element;
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/jsx-runtime").JSX.Element)>;
13
+ }) => import("react").JSX.Element)>;
14
14
  export {};
@@ -4,4 +4,5 @@ export * from './createQpqRuntimeDefinition';
4
4
  export * from './QpqContextProvider';
5
5
  export * from './QpqMappedApi';
6
6
  export * from './QpqRuntimeEffectCatcher';
7
+ export * from './useBubbleReducer';
7
8
  export * from './useQpqRuntime';
@@ -4,4 +4,5 @@ export * from './createQpqRuntimeDefinition';
4
4
  export * from './QpqContextProvider';
5
5
  export * from './QpqMappedApi';
6
6
  export * from './QpqRuntimeEffectCatcher';
7
+ export * from './useBubbleReducer';
7
8
  export * from './useQpqRuntime';
@@ -0,0 +1,2 @@
1
+ import { QpqBubbleReducer } from './bubbleReducer';
2
+ export declare const useBubbleReducer: <TState>(reducer: QpqBubbleReducer<TState, any>, initialState: TState | (() => TState)) => [TState, (action: unknown) => void];
@@ -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 declare function useQpqRuntime<TState, TAction, TApi extends QpqApi>(atom: QpqRuntimeDefinition<TState, TAction, TApi>, mainStory?: Story<any, any>, name?: string): [QpqMappedApi<TApi>, TState, (action: any) => void];
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 resolver = useQpq(getStateActionProcessor(dispatch, getCurrentState));
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 = {};
@@ -1,5 +1,6 @@
1
1
  export * from './asmj';
2
2
  export * from './useAsyncEffect';
3
+ export * from './useComputed';
3
4
  export * from './useEffectCallback';
4
5
  export * from './useFastCallback';
5
6
  export * from './useMetadata';
@@ -1,5 +1,6 @@
1
1
  export * from './asmj';
2
2
  export * from './useAsyncEffect';
3
+ export * from './useComputed';
3
4
  export * from './useEffectCallback';
4
5
  export * from './useFastCallback';
5
6
  export * from './useMetadata';
@@ -0,0 +1 @@
1
+ export declare function useComputed<TState extends object, T>(state: TState, selector: (state: TState) => T): T;
@@ -0,0 +1,4 @@
1
+ export function useComputed(state, selector) {
2
+ // TODO: use proxies to resolve deps and only return updated values when the deps change.
3
+ return selector(state);
4
+ }
@@ -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
@@ -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
+ });
@@ -1,2 +1,4 @@
1
+ export * from './actionProcessor/getServiceRequestActionProcessor';
2
+ export * from './useQpqWebsocketQueueRuntime';
1
3
  export * from './useSubscribeToWebSocketQueueEvent';
2
4
  export * from './useWebsocketQueueSendEvent';
@@ -1,2 +1,4 @@
1
+ export * from './actionProcessor/getServiceRequestActionProcessor';
2
+ export * from './useQpqWebsocketQueueRuntime';
1
3
  export * from './useSubscribeToWebSocketQueueEvent';
2
4
  export * from './useWebsocketQueueSendEvent';
@@ -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: <E extends AnyWebSocketQueueEventMessageWithCorrelation>() => (event: E) => void;
2
+ export declare const useWebsocketQueueSendEvent: <TSend extends AnyWebSocketQueueEventMessageWithCorrelation>(onCorrelatedMessage?: (event: AnyWebSocketQueueEventMessageWithCorrelation) => void) => (event: Omit<TSend, "correlationId">) => Promise<any>;
@@ -1,5 +1,55 @@
1
- import { useWebsocketSendEvent } from '../../websocket';
2
- export const useWebsocketQueueSendEvent = () => {
3
- const sendMessage = useWebsocketSendEvent();
4
- return sendMessage;
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>;
@@ -0,0 +1,2 @@
1
+ import { createContext } from 'react';
2
+ export const WebSocketAuthContext = createContext(false);
@@ -1,11 +1,11 @@
1
- import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
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
- // This fragment is not useless!
9
- return _jsx(_Fragment, { children: children });
9
+ return (_jsx(WebSocketAuthContext.Provider, { value: isAuthenticated, children: children }));
10
10
  };
11
11
  export const WebSocketAuthSync = memo(component);
@@ -1,2 +1,3 @@
1
1
  export * from './useWebsocketAuthSync';
2
+ export * from './useWebSocketIsAuthenticated';
2
3
  export * from './useWebsocketPingPong';
@@ -1,2 +1,3 @@
1
1
  export * from './useWebsocketAuthSync';
2
+ export * from './useWebSocketIsAuthenticated';
2
3
  export * from './useWebsocketPingPong';
@@ -0,0 +1 @@
1
+ export declare const useWebSocketIsAuthenticated: () => boolean;
@@ -0,0 +1,5 @@
1
+ import { useContext } from 'react';
2
+ import { WebSocketAuthContext } from '../WebSocketAuthContext';
3
+ export const useWebSocketIsAuthenticated = () => {
4
+ return useContext(WebSocketAuthContext);
5
+ };
@@ -1,2 +1,2 @@
1
1
  import { AuthenticationInfo } from 'quidproquo-core';
2
- export declare const useWebsocketAuthSync: (accessToken: AuthenticationInfo["accessToken"]) => void;
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
- const authMessage = {
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
- const authMessage = {
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 +1,2 @@
1
+ export * from './hooks/useWebSocketIsAuthenticated';
1
2
  export * from './WebSocketAuthSync';
@@ -1 +1,2 @@
1
+ export * from './hooks/useWebSocketIsAuthenticated';
1
2
  export * from './WebSocketAuthSync';
@@ -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, callback);
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, callback]);
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, callback);
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, callback]);
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.262",
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
- "quidproquo-core": "0.0.262",
36
- "quidproquo-tsconfig": "0.0.262",
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
- "symbol-observable": "^4.0.0",
45
- "quidproquo-web": "0.0.262",
46
- "quidproquo-webserver": "0.0.262",
47
- "quidproquo-actionprocessor-node": "0.0.262",
48
- "quidproquo-actionprocessor-web": "0.0.262"
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
+ }