2019-01-03 17:14:25 +03:00
|
|
|
import {
|
2024-06-12 16:18:46 +03:00
|
|
|
DispatchAction,
|
2019-01-10 21:51:14 +03:00
|
|
|
GET_REPORT_ERROR,
|
2024-06-12 16:18:46 +03:00
|
|
|
GET_REPORT_REQUEST,
|
2020-08-08 23:26:39 +03:00
|
|
|
GET_REPORT_SUCCESS,
|
2024-06-12 16:18:46 +03:00
|
|
|
CLEAR_INSTANCES,
|
|
|
|
getActiveInstance,
|
2020-10-26 02:32:04 +03:00
|
|
|
importState,
|
2024-06-12 16:18:46 +03:00
|
|
|
LIFTED_ACTION,
|
2020-10-26 02:32:04 +03:00
|
|
|
LiftedActionAction,
|
2024-06-12 16:18:46 +03:00
|
|
|
REMOVE_INSTANCE,
|
2020-10-26 02:32:04 +03:00
|
|
|
Request,
|
2024-06-12 16:18:46 +03:00
|
|
|
showNotification,
|
|
|
|
UPDATE_REPORTS,
|
|
|
|
UPDATE_STATE,
|
2020-10-26 02:32:04 +03:00
|
|
|
UpdateReportsRequest,
|
2024-06-12 16:18:46 +03:00
|
|
|
} from '@redux-devtools/app-core';
|
|
|
|
import socketClusterClient, { AGClientSocket } from 'socketcluster-client';
|
|
|
|
import { stringify } from 'jsan';
|
2024-08-06 06:11:13 +03:00
|
|
|
import { Dispatch, Middleware, MiddlewareAPI } from 'redux';
|
2024-06-12 16:18:46 +03:00
|
|
|
import * as actions from '../constants/socketActionTypes';
|
2019-01-03 17:14:25 +03:00
|
|
|
import { nonReduxDispatch } from '../utils/monitorActions';
|
2024-06-12 16:18:46 +03:00
|
|
|
import { EmitAction, StoreAction } from '../actions';
|
2020-10-26 02:32:04 +03:00
|
|
|
import { StoreState } from '../reducers';
|
2019-01-03 17:14:25 +03:00
|
|
|
|
2022-06-13 14:35:11 +03:00
|
|
|
let socket: AGClientSocket;
|
2020-10-26 02:32:04 +03:00
|
|
|
let store: MiddlewareAPI<Dispatch<StoreAction>, StoreState>;
|
2019-01-03 17:14:25 +03:00
|
|
|
|
2020-10-26 02:32:04 +03:00
|
|
|
function emit({ message: type, id, instanceId, action, state }: EmitAction) {
|
2022-06-13 14:35:11 +03:00
|
|
|
void socket.transmit(id ? `sc-${id}` : 'respond', {
|
|
|
|
type,
|
|
|
|
action,
|
|
|
|
state,
|
|
|
|
instanceId,
|
|
|
|
});
|
2019-01-03 17:14:25 +03:00
|
|
|
}
|
|
|
|
|
2020-10-26 02:32:04 +03:00
|
|
|
function startMonitoring(channel: string) {
|
2019-01-03 17:14:25 +03:00
|
|
|
if (channel !== store.getState().socket.baseChannel) return;
|
|
|
|
store.dispatch({ type: actions.EMIT, message: 'START' });
|
|
|
|
}
|
|
|
|
|
2020-10-26 02:32:04 +03:00
|
|
|
function dispatchRemoteAction({
|
|
|
|
message,
|
|
|
|
action,
|
|
|
|
state,
|
|
|
|
toAll,
|
|
|
|
}: LiftedActionAction) {
|
2019-01-03 17:14:25 +03:00
|
|
|
const instances = store.getState().instances;
|
|
|
|
const instanceId = getActiveInstance(instances);
|
|
|
|
const id = !toAll && instances.options[instanceId].connectionId;
|
|
|
|
store.dispatch({
|
|
|
|
type: actions.EMIT,
|
|
|
|
message,
|
|
|
|
action,
|
2019-01-10 21:51:14 +03:00
|
|
|
state: nonReduxDispatch(
|
|
|
|
store,
|
|
|
|
message,
|
|
|
|
instanceId,
|
2020-10-26 02:32:04 +03:00
|
|
|
action as DispatchAction,
|
2019-01-10 21:51:14 +03:00
|
|
|
state,
|
2023-07-12 21:03:20 +03:00
|
|
|
instances,
|
2019-01-10 21:51:14 +03:00
|
|
|
),
|
2019-01-03 17:14:25 +03:00
|
|
|
instanceId,
|
2020-08-08 23:26:39 +03:00
|
|
|
id,
|
2019-01-03 17:14:25 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-10-26 02:32:04 +03:00
|
|
|
interface RequestBase {
|
|
|
|
id?: string;
|
|
|
|
instanceId?: string;
|
|
|
|
}
|
|
|
|
interface DisconnectedAction extends RequestBase {
|
|
|
|
type: 'DISCONNECTED';
|
|
|
|
id: string;
|
|
|
|
}
|
|
|
|
interface StartAction extends RequestBase {
|
|
|
|
type: 'START';
|
|
|
|
id: string;
|
|
|
|
}
|
|
|
|
interface ErrorAction extends RequestBase {
|
|
|
|
type: 'ERROR';
|
|
|
|
payload: string;
|
|
|
|
}
|
|
|
|
interface RequestWithData extends RequestBase {
|
|
|
|
data: Request;
|
|
|
|
}
|
|
|
|
type MonitoringRequest =
|
|
|
|
| DisconnectedAction
|
|
|
|
| StartAction
|
|
|
|
| ErrorAction
|
|
|
|
| Request;
|
|
|
|
|
|
|
|
function monitoring(request: MonitoringRequest) {
|
2019-01-03 17:14:25 +03:00
|
|
|
if (request.type === 'DISCONNECTED') {
|
|
|
|
store.dispatch({
|
|
|
|
type: REMOVE_INSTANCE,
|
2020-08-08 23:26:39 +03:00
|
|
|
id: request.id,
|
2019-01-03 17:14:25 +03:00
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (request.type === 'START') {
|
|
|
|
store.dispatch({ type: actions.EMIT, message: 'START', id: request.id });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (request.type === 'ERROR') {
|
|
|
|
store.dispatch(showNotification(request.payload));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
store.dispatch({
|
|
|
|
type: UPDATE_STATE,
|
2021-06-18 06:56:36 +03:00
|
|
|
request: (request as unknown as RequestWithData).data
|
|
|
|
? { ...(request as unknown as RequestWithData).data, id: request.id }
|
2020-10-26 02:32:04 +03:00
|
|
|
: request,
|
2019-01-03 17:14:25 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
const instances = store.getState().instances;
|
|
|
|
const instanceId = request.instanceId || request.id;
|
|
|
|
if (
|
2019-01-10 21:51:14 +03:00
|
|
|
instances.sync &&
|
|
|
|
instanceId === instances.selected &&
|
2019-01-03 17:14:25 +03:00
|
|
|
(request.type === 'ACTION' || request.type === 'STATE')
|
|
|
|
) {
|
2022-06-13 14:35:11 +03:00
|
|
|
void socket.transmit('respond', {
|
2019-01-03 17:14:25 +03:00
|
|
|
type: 'SYNC',
|
|
|
|
state: stringify(instances.states[instanceId]),
|
|
|
|
id: request.id,
|
2020-08-08 23:26:39 +03:00
|
|
|
instanceId,
|
2019-01-03 17:14:25 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-26 02:32:04 +03:00
|
|
|
function subscribe(
|
|
|
|
channelName: string,
|
2023-07-12 21:03:20 +03:00
|
|
|
subscription: typeof UPDATE_STATE | typeof UPDATE_REPORTS,
|
2020-10-26 02:32:04 +03:00
|
|
|
) {
|
2019-01-03 17:14:25 +03:00
|
|
|
const channel = socket.subscribe(channelName);
|
2022-06-13 14:35:11 +03:00
|
|
|
if (subscription === UPDATE_STATE) {
|
|
|
|
void (async () => {
|
|
|
|
for await (const data of channel) {
|
|
|
|
monitoring(data as MonitoringRequest);
|
|
|
|
}
|
|
|
|
})();
|
|
|
|
} else {
|
2020-10-26 02:32:04 +03:00
|
|
|
const watcher = (request: UpdateReportsRequest) => {
|
2019-01-03 17:14:25 +03:00
|
|
|
store.dispatch({ type: subscription, request });
|
|
|
|
};
|
2022-06-13 14:35:11 +03:00
|
|
|
void (async () => {
|
|
|
|
for await (const data of channel) {
|
|
|
|
watcher(data as UpdateReportsRequest);
|
|
|
|
}
|
|
|
|
})();
|
2019-01-03 17:14:25 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleConnection() {
|
2022-06-13 14:35:11 +03:00
|
|
|
void (async () => {
|
|
|
|
for await (const data of socket.listener('connect')) {
|
|
|
|
store.dispatch({
|
|
|
|
type: actions.CONNECT_SUCCESS,
|
|
|
|
payload: {
|
|
|
|
id: data.id,
|
|
|
|
authState: socket.authState,
|
|
|
|
socketState: socket.state,
|
|
|
|
},
|
|
|
|
// @ts-expect-error Is this legitimate?
|
|
|
|
error: data.authError,
|
|
|
|
});
|
|
|
|
if (socket.authState !== actions.AUTHENTICATED) {
|
|
|
|
store.dispatch({ type: actions.AUTH_REQUEST });
|
|
|
|
}
|
2019-01-03 17:14:25 +03:00
|
|
|
}
|
2022-06-13 14:35:11 +03:00
|
|
|
})();
|
|
|
|
void (async () => {
|
|
|
|
for await (const data of socket.listener('disconnect')) {
|
|
|
|
store.dispatch({ type: actions.DISCONNECTED, code: data.code });
|
2024-06-12 16:18:46 +03:00
|
|
|
store.dispatch({ type: CLEAR_INSTANCES });
|
2022-06-13 14:35:11 +03:00
|
|
|
}
|
|
|
|
})();
|
2019-01-03 17:14:25 +03:00
|
|
|
|
2022-06-13 14:35:11 +03:00
|
|
|
void (async () => {
|
|
|
|
for await (const data of socket.listener('subscribe')) {
|
|
|
|
store.dispatch({
|
|
|
|
type: actions.SUBSCRIBE_SUCCESS,
|
|
|
|
channel: data.channel,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
})();
|
|
|
|
void (async () => {
|
|
|
|
for await (const data of socket.listener('unsubscribe')) {
|
|
|
|
void socket.unsubscribe(data.channel);
|
|
|
|
store.dispatch({ type: actions.UNSUBSCRIBE, channel: data.channel });
|
|
|
|
}
|
|
|
|
})();
|
|
|
|
void (async () => {
|
|
|
|
for await (const data of socket.listener('subscribeFail')) {
|
|
|
|
store.dispatch({
|
|
|
|
type: actions.SUBSCRIBE_ERROR,
|
|
|
|
error: data.error,
|
|
|
|
status: 'subscribeFail',
|
|
|
|
});
|
|
|
|
}
|
|
|
|
})();
|
2019-01-03 17:14:25 +03:00
|
|
|
|
2022-06-13 14:35:11 +03:00
|
|
|
void (async () => {
|
|
|
|
for await (const data of socket.listener('error')) {
|
|
|
|
store.dispatch({ type: actions.CONNECT_ERROR, error: data.error });
|
|
|
|
}
|
|
|
|
})();
|
2019-01-03 17:14:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
function connect() {
|
|
|
|
if (process.env.NODE_ENV === 'test') return;
|
|
|
|
const connection = store.getState().connection;
|
|
|
|
try {
|
2022-06-13 14:35:11 +03:00
|
|
|
socket = socketClusterClient.create(connection.options);
|
2020-10-26 02:32:04 +03:00
|
|
|
handleConnection();
|
2019-01-03 17:14:25 +03:00
|
|
|
} catch (error) {
|
2021-10-05 05:32:03 +03:00
|
|
|
store.dispatch({ type: actions.CONNECT_ERROR, error: error as Error });
|
|
|
|
store.dispatch(
|
2023-07-12 21:03:20 +03:00
|
|
|
showNotification((error as Error).message || (error as string)),
|
2021-10-05 05:32:03 +03:00
|
|
|
);
|
2019-01-03 17:14:25 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function disconnect() {
|
2021-09-06 21:29:41 +03:00
|
|
|
if (socket) {
|
|
|
|
socket.disconnect();
|
|
|
|
}
|
2019-01-03 17:14:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
function login() {
|
2022-06-13 14:35:11 +03:00
|
|
|
void (async () => {
|
|
|
|
try {
|
|
|
|
const baseChannel = (await socket.invoke('login', {})) as string;
|
|
|
|
store.dispatch({ type: actions.AUTH_SUCCESS, baseChannel });
|
|
|
|
store.dispatch({
|
|
|
|
type: actions.SUBSCRIBE_REQUEST,
|
|
|
|
channel: baseChannel,
|
|
|
|
subscription: UPDATE_STATE,
|
|
|
|
});
|
|
|
|
store.dispatch({
|
|
|
|
type: actions.SUBSCRIBE_REQUEST,
|
|
|
|
channel: 'report',
|
|
|
|
subscription: UPDATE_REPORTS,
|
|
|
|
});
|
|
|
|
} catch (error) {
|
|
|
|
store.dispatch({ type: actions.AUTH_ERROR, error: error as Error });
|
2019-01-03 17:14:25 +03:00
|
|
|
}
|
2022-06-13 14:35:11 +03:00
|
|
|
})();
|
2019-01-03 17:14:25 +03:00
|
|
|
}
|
|
|
|
|
2020-10-26 02:32:04 +03:00
|
|
|
function getReport(reportId: unknown) {
|
2022-06-13 14:35:11 +03:00
|
|
|
void (async () => {
|
|
|
|
try {
|
|
|
|
const data = (await socket.invoke('getReport', reportId)) as {
|
|
|
|
payload: string;
|
|
|
|
};
|
2020-10-26 02:32:04 +03:00
|
|
|
store.dispatch({ type: GET_REPORT_SUCCESS, data });
|
|
|
|
store.dispatch(importState(data.payload));
|
2022-06-13 14:35:11 +03:00
|
|
|
} catch (error) {
|
|
|
|
store.dispatch({ type: GET_REPORT_ERROR, error: error as Error });
|
2019-01-03 17:14:25 +03:00
|
|
|
}
|
2022-06-13 14:35:11 +03:00
|
|
|
})();
|
2019-01-03 17:14:25 +03:00
|
|
|
}
|
|
|
|
|
2024-08-06 06:11:13 +03:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
|
|
export const api: Middleware<{}, StoreState, Dispatch<StoreAction>> = (
|
|
|
|
inStore,
|
|
|
|
) => {
|
2019-01-03 17:14:25 +03:00
|
|
|
store = inStore;
|
2024-08-06 06:11:13 +03:00
|
|
|
return (next) => (untypedAction) => {
|
|
|
|
const result = next(untypedAction);
|
|
|
|
|
|
|
|
const action = untypedAction as StoreAction;
|
2020-10-26 02:32:04 +03:00
|
|
|
switch (action.type) {
|
2019-01-10 21:51:14 +03:00
|
|
|
case actions.CONNECT_REQUEST:
|
|
|
|
connect();
|
|
|
|
break;
|
2019-01-03 17:14:25 +03:00
|
|
|
case actions.RECONNECT:
|
|
|
|
disconnect();
|
|
|
|
if (action.options.type !== 'disabled') connect();
|
|
|
|
break;
|
2019-01-10 21:51:14 +03:00
|
|
|
case actions.AUTH_REQUEST:
|
|
|
|
login();
|
|
|
|
break;
|
|
|
|
case actions.SUBSCRIBE_REQUEST:
|
|
|
|
subscribe(action.channel, action.subscription);
|
|
|
|
break;
|
|
|
|
case actions.SUBSCRIBE_SUCCESS:
|
|
|
|
startMonitoring(action.channel);
|
|
|
|
break;
|
|
|
|
case actions.EMIT:
|
|
|
|
if (socket) emit(action);
|
|
|
|
break;
|
|
|
|
case LIFTED_ACTION:
|
|
|
|
dispatchRemoteAction(action);
|
|
|
|
break;
|
|
|
|
case GET_REPORT_REQUEST:
|
|
|
|
getReport(action.report);
|
|
|
|
break;
|
2019-01-03 17:14:25 +03:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
};
|
2024-08-06 06:11:13 +03:00
|
|
|
};
|