2021-09-15 19:09:59 -07:00
|
|
|
import WebSocketClient from '@gamestdio/websocket';
|
2022-01-10 14:25:06 -08:00
|
|
|
|
2021-03-24 15:53:09 -07:00
|
|
|
import { getAccessToken } from 'soapbox/utils/auth';
|
2020-03-27 13:59:38 -07:00
|
|
|
|
2022-06-18 11:58:42 -07:00
|
|
|
import type { AppDispatch, RootState } from 'soapbox/store';
|
2020-03-27 13:59:38 -07:00
|
|
|
|
2022-06-18 11:58:42 -07:00
|
|
|
const randomIntUpTo = (max: number) => Math.floor(Math.random() * Math.floor(max));
|
|
|
|
|
2023-04-02 17:54:08 -07:00
|
|
|
interface ConnectStreamCallbacks {
|
2023-10-02 11:54:02 -07:00
|
|
|
onConnect(): void;
|
|
|
|
onDisconnect(): void;
|
|
|
|
onReceive(websocket: WebSocket, data: unknown): void;
|
2023-04-02 17:54:08 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
type PollingRefreshFn = (dispatch: AppDispatch, done?: () => void) => void
|
|
|
|
|
2022-06-18 11:58:42 -07:00
|
|
|
export function connectStream(
|
|
|
|
path: string,
|
2023-04-02 17:54:08 -07:00
|
|
|
pollingRefresh: PollingRefreshFn | null = null,
|
|
|
|
callbacks: (dispatch: AppDispatch, getState: () => RootState) => ConnectStreamCallbacks,
|
2022-06-18 11:58:42 -07:00
|
|
|
) {
|
|
|
|
return (dispatch: AppDispatch, getState: () => RootState) => {
|
2023-10-27 12:18:30 -07:00
|
|
|
const streamingAPIBaseURL = getState().instance.configuration.urls.streaming;
|
2021-03-24 15:53:09 -07:00
|
|
|
const accessToken = getAccessToken(getState());
|
2020-03-27 13:59:38 -07:00
|
|
|
const { onConnect, onDisconnect, onReceive } = callbacks(dispatch, getState);
|
|
|
|
|
2022-06-18 11:58:42 -07:00
|
|
|
let polling: NodeJS.Timeout | null = null;
|
2020-03-27 13:59:38 -07:00
|
|
|
|
|
|
|
const setupPolling = () => {
|
2022-06-18 11:58:42 -07:00
|
|
|
if (pollingRefresh) {
|
|
|
|
pollingRefresh(dispatch, () => {
|
|
|
|
polling = setTimeout(() => setupPolling(), 20000 + randomIntUpTo(20000));
|
|
|
|
});
|
|
|
|
}
|
2020-03-27 13:59:38 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
const clearPolling = () => {
|
|
|
|
if (polling) {
|
|
|
|
clearTimeout(polling);
|
|
|
|
polling = null;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-04-02 17:54:08 -07:00
|
|
|
let subscription: WebSocket;
|
2020-03-27 13:59:38 -07:00
|
|
|
|
2021-10-20 14:36:38 -07:00
|
|
|
// If the WebSocket fails to be created, don't crash the whole page,
|
|
|
|
// just proceed without a subscription.
|
|
|
|
try {
|
2023-06-23 19:41:36 -07:00
|
|
|
subscription = getStream(streamingAPIBaseURL!, accessToken!, path, {
|
2021-10-20 14:36:38 -07:00
|
|
|
connected() {
|
|
|
|
if (pollingRefresh) {
|
|
|
|
clearPolling();
|
|
|
|
}
|
|
|
|
|
|
|
|
onConnect();
|
|
|
|
},
|
2020-03-27 13:59:38 -07:00
|
|
|
|
2021-10-20 14:36:38 -07:00
|
|
|
disconnected() {
|
|
|
|
if (pollingRefresh) {
|
|
|
|
polling = setTimeout(() => setupPolling(), randomIntUpTo(40000));
|
|
|
|
}
|
2020-03-27 13:59:38 -07:00
|
|
|
|
2021-10-20 14:36:38 -07:00
|
|
|
onDisconnect();
|
|
|
|
},
|
2020-03-27 13:59:38 -07:00
|
|
|
|
2021-10-20 14:36:38 -07:00
|
|
|
received(data) {
|
2023-04-02 17:54:08 -07:00
|
|
|
onReceive(subscription, data);
|
2021-10-20 14:36:38 -07:00
|
|
|
},
|
2020-03-27 13:59:38 -07:00
|
|
|
|
2021-10-20 14:36:38 -07:00
|
|
|
reconnected() {
|
|
|
|
if (pollingRefresh) {
|
|
|
|
clearPolling();
|
|
|
|
pollingRefresh(dispatch);
|
|
|
|
}
|
2020-03-27 13:59:38 -07:00
|
|
|
|
2021-10-20 14:36:38 -07:00
|
|
|
onConnect();
|
|
|
|
},
|
2020-03-27 13:59:38 -07:00
|
|
|
|
2021-10-20 14:36:38 -07:00
|
|
|
});
|
|
|
|
} catch (e) {
|
|
|
|
console.error(e);
|
|
|
|
}
|
2020-03-27 13:59:38 -07:00
|
|
|
|
|
|
|
const disconnect = () => {
|
|
|
|
if (subscription) {
|
|
|
|
subscription.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
clearPolling();
|
|
|
|
};
|
|
|
|
|
|
|
|
return disconnect;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-06-18 11:58:42 -07:00
|
|
|
export default function getStream(
|
|
|
|
streamingAPIBaseURL: string,
|
|
|
|
accessToken: string,
|
|
|
|
stream: string,
|
|
|
|
{ connected, received, disconnected, reconnected }: {
|
2023-10-02 11:54:02 -07:00
|
|
|
connected: ((this: WebSocket, ev: Event) => any) | null;
|
|
|
|
received: (data: any) => void;
|
|
|
|
disconnected: ((this: WebSocket, ev: Event) => any) | null;
|
|
|
|
reconnected: ((this: WebSocket, ev: Event) => any);
|
2022-06-18 11:58:42 -07:00
|
|
|
},
|
|
|
|
) {
|
2020-03-27 13:59:38 -07:00
|
|
|
const params = [ `stream=${stream}` ];
|
|
|
|
|
2022-06-18 11:58:42 -07:00
|
|
|
const ws = new WebSocketClient(`${streamingAPIBaseURL}/api/v1/streaming/?${params.join('&')}`, accessToken as any);
|
2020-03-27 13:59:38 -07:00
|
|
|
|
|
|
|
ws.onopen = connected;
|
|
|
|
ws.onclose = disconnected;
|
|
|
|
ws.onreconnect = reconnected;
|
|
|
|
|
2020-04-10 18:05:40 -07:00
|
|
|
ws.onmessage = (e) => {
|
|
|
|
if (!e.data) return;
|
|
|
|
try {
|
|
|
|
received(JSON.parse(e.data));
|
2022-04-11 12:58:48 -07:00
|
|
|
} catch (error) {
|
2020-04-10 18:05:40 -07:00
|
|
|
console.error(e);
|
2020-04-14 11:44:40 -07:00
|
|
|
console.error(`Could not parse the above streaming event.\n${error}`);
|
2020-04-10 18:05:40 -07:00
|
|
|
}
|
2020-04-14 11:44:40 -07:00
|
|
|
};
|
2020-04-10 18:05:40 -07:00
|
|
|
|
2020-03-27 13:59:38 -07:00
|
|
|
return ws;
|
2021-08-03 12:22:51 -07:00
|
|
|
}
|