mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 09:30:31 +03:00
Updated mercure integration so that the hook accepts a list of topics to subscribe
This commit is contained in:
parent
71ee886e24
commit
9904ac757b
10 changed files with 41 additions and 21 deletions
7
src/mercure/helpers/Topics.ts
Normal file
7
src/mercure/helpers/Topics.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export class Topics {
|
||||||
|
public static visits = () => 'https://shlink.io/new-visit';
|
||||||
|
|
||||||
|
public static shortUrlVisits = (shortCode: string) => `https://shlink.io/new-visit/${shortCode}`;
|
||||||
|
|
||||||
|
public static orphanVisits = () => 'https://shlink.io/new-orphan-visit';
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ export interface MercureBoundProps {
|
||||||
|
|
||||||
export function boundToMercureHub<T = {}>(
|
export function boundToMercureHub<T = {}>(
|
||||||
WrappedComponent: FC<MercureBoundProps & T>,
|
WrappedComponent: FC<MercureBoundProps & T>,
|
||||||
getTopicForProps: (props: T) => string,
|
getTopicsForProps: (props: T) => string[],
|
||||||
) {
|
) {
|
||||||
const pendingUpdates = new Set<CreateVisit>();
|
const pendingUpdates = new Set<CreateVisit>();
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ export function boundToMercureHub<T = {}>(
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onMessage = (visit: CreateVisit) => interval ? pendingUpdates.add(visit) : createNewVisits([ visit ]);
|
const onMessage = (visit: CreateVisit) => interval ? pendingUpdates.add(visit) : createNewVisits([ visit ]);
|
||||||
const closeEventSource = bindToMercureTopic(mercureInfo, getTopicForProps(props), onMessage, loadMercureInfo);
|
const closeEventSource = bindToMercureTopic(mercureInfo, getTopicsForProps(props), onMessage, loadMercureInfo);
|
||||||
|
|
||||||
if (!interval) {
|
if (!interval) {
|
||||||
return closeEventSource;
|
return closeEventSource;
|
||||||
|
|
|
@ -1,24 +1,31 @@
|
||||||
import { EventSourcePolyfill as EventSource } from 'event-source-polyfill';
|
import { EventSourcePolyfill as EventSource } from 'event-source-polyfill';
|
||||||
import { MercureInfo } from '../reducers/mercureInfo';
|
import { MercureInfo } from '../reducers/mercureInfo';
|
||||||
|
|
||||||
export const bindToMercureTopic = <T>(mercureInfo: MercureInfo, topic: string, onMessage: (message: T) => void, onTokenExpired: Function) => { // eslint-disable-line max-len
|
export const bindToMercureTopic = <T>(mercureInfo: MercureInfo, topics: string[], onMessage: (message: T) => void, onTokenExpired: Function) => { // eslint-disable-line max-len
|
||||||
const { mercureHubUrl, token, loading, error } = mercureInfo;
|
const { mercureHubUrl, token, loading, error } = mercureInfo;
|
||||||
|
|
||||||
if (loading || error || !mercureHubUrl) {
|
if (loading || error || !mercureHubUrl) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hubUrl = new URL(mercureHubUrl);
|
const onEventSourceMessage = ({ data }: { data: string }) => onMessage(JSON.parse(data) as T);
|
||||||
|
const onEventSourceError = ({ status }: { status: number }) => status === 401 && onTokenExpired();
|
||||||
|
|
||||||
hubUrl.searchParams.append('topic', topic);
|
const subscriptions: EventSource[] = topics.map((topic) => {
|
||||||
const es = new EventSource(hubUrl, {
|
const hubUrl = new URL(mercureHubUrl);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`,
|
hubUrl.searchParams.append('topic', topic);
|
||||||
},
|
const es = new EventSource(hubUrl, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
es.onmessage = onEventSourceMessage;
|
||||||
|
es.onerror = onEventSourceError;
|
||||||
|
|
||||||
|
return es;
|
||||||
});
|
});
|
||||||
|
|
||||||
es.onmessage = ({ data }: { data: string }) => onMessage(JSON.parse(data) as T);
|
return () => subscriptions.forEach((es) => es.close());
|
||||||
es.onerror = ({ status }: { status: number }) => status === 401 && onTokenExpired();
|
|
||||||
|
|
||||||
return () => es.close();
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
||||||
import { CreateShortUrlProps } from '../short-urls/CreateShortUrl';
|
import { CreateShortUrlProps } from '../short-urls/CreateShortUrl';
|
||||||
import { VisitsOverview } from '../visits/reducers/visitsOverview';
|
import { VisitsOverview } from '../visits/reducers/visitsOverview';
|
||||||
import { Versions } from '../utils/helpers/version';
|
import { Versions } from '../utils/helpers/version';
|
||||||
|
import { Topics } from '../mercure/helpers/Topics';
|
||||||
import { isServerWithId, SelectedServer } from './data';
|
import { isServerWithId, SelectedServer } from './data';
|
||||||
import './Overview.scss';
|
import './Overview.scss';
|
||||||
|
|
||||||
|
@ -119,4 +120,4 @@ export const Overview = (
|
||||||
</Card>
|
</Card>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}, () => 'https://shlink.io/new-visit');
|
}, () => [ Topics.visits(), Topics.orphanVisits() ]);
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { determineOrderDir, OrderDir } from '../utils/utils';
|
||||||
import { isReachableServer, SelectedServer } from '../servers/data';
|
import { isReachableServer, SelectedServer } from '../servers/data';
|
||||||
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
||||||
import { parseQuery } from '../utils/helpers/query';
|
import { parseQuery } from '../utils/helpers/query';
|
||||||
|
import { Topics } from '../mercure/helpers/Topics';
|
||||||
import { ShortUrlsList as ShortUrlsListState } from './reducers/shortUrlsList';
|
import { ShortUrlsList as ShortUrlsListState } from './reducers/shortUrlsList';
|
||||||
import { OrderableFields, ShortUrlsListParams, SORTABLE_FIELDS } from './reducers/shortUrlsListParams';
|
import { OrderableFields, ShortUrlsListParams, SORTABLE_FIELDS } from './reducers/shortUrlsListParams';
|
||||||
import { ShortUrlsTableProps } from './ShortUrlsTable';
|
import { ShortUrlsTableProps } from './ShortUrlsTable';
|
||||||
|
@ -98,6 +99,6 @@ const ShortUrlsList = (ShortUrlsTable: FC<ShortUrlsTableProps>) => boundToMercur
|
||||||
</Card>
|
</Card>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}, () => 'https://shlink.io/new-visit');
|
}, () => [ Topics.visits() ]);
|
||||||
|
|
||||||
export default ShortUrlsList;
|
export default ShortUrlsList;
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { Result } from '../utils/Result';
|
||||||
import { ShlinkApiError } from '../api/ShlinkApiError';
|
import { ShlinkApiError } from '../api/ShlinkApiError';
|
||||||
import { TagsList as TagsListState } from './reducers/tagsList';
|
import { TagsList as TagsListState } from './reducers/tagsList';
|
||||||
import { TagCardProps } from './TagCard';
|
import { TagCardProps } from './TagCard';
|
||||||
|
import { Topics } from '../mercure/helpers/Topics';
|
||||||
|
|
||||||
const { ceil } = Math;
|
const { ceil } = Math;
|
||||||
const TAGS_GROUPS_AMOUNT = 4;
|
const TAGS_GROUPS_AMOUNT = 4;
|
||||||
|
@ -75,6 +76,6 @@ const TagsList = (TagCard: FC<TagCardProps>) => boundToMercureHub((
|
||||||
{renderContent()}
|
{renderContent()}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}, () => 'https://shlink.io/new-visit');
|
}, () => [ Topics.visits() ]);
|
||||||
|
|
||||||
export default TagsList;
|
export default TagsList;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { RouteComponentProps } from 'react-router';
|
import { RouteComponentProps } from 'react-router';
|
||||||
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
||||||
import { ShlinkVisitsParams } from '../api/types';
|
import { ShlinkVisitsParams } from '../api/types';
|
||||||
|
import { Topics } from '../mercure/helpers/Topics';
|
||||||
import { TagVisits as TagVisitsState } from './reducers/tagVisits';
|
import { TagVisits as TagVisitsState } from './reducers/tagVisits';
|
||||||
import VisitsStats from './VisitsStats';
|
import VisitsStats from './VisitsStats';
|
||||||
import { OrphanVisitsHeader } from './OrphanVisitsHeader';
|
import { OrphanVisitsHeader } from './OrphanVisitsHeader';
|
||||||
|
@ -26,4 +27,4 @@ export const OrphanVisits = boundToMercureHub(({
|
||||||
>
|
>
|
||||||
<OrphanVisitsHeader orphanVisits={orphanVisits} goBack={goBack} />
|
<OrphanVisitsHeader orphanVisits={orphanVisits} goBack={goBack} />
|
||||||
</VisitsStats>
|
</VisitsStats>
|
||||||
), () => 'https://shlink.io/new-orphan-visit');
|
), () => [ Topics.orphanVisits() ]);
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { RouteComponentProps } from 'react-router';
|
||||||
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
||||||
import { ShlinkVisitsParams } from '../api/types';
|
import { ShlinkVisitsParams } from '../api/types';
|
||||||
import { parseQuery } from '../utils/helpers/query';
|
import { parseQuery } from '../utils/helpers/query';
|
||||||
|
import { Topics } from '../mercure/helpers/Topics';
|
||||||
import { ShortUrlVisits as ShortUrlVisitsState } from './reducers/shortUrlVisits';
|
import { ShortUrlVisits as ShortUrlVisitsState } from './reducers/shortUrlVisits';
|
||||||
import ShortUrlVisitsHeader from './ShortUrlVisitsHeader';
|
import ShortUrlVisitsHeader from './ShortUrlVisitsHeader';
|
||||||
import { ShortUrlDetail } from './reducers/shortUrlDetail';
|
import { ShortUrlDetail } from './reducers/shortUrlDetail';
|
||||||
|
@ -45,6 +46,6 @@ const ShortUrlVisits = boundToMercureHub(({
|
||||||
<ShortUrlVisitsHeader shortUrlDetail={shortUrlDetail} shortUrlVisits={shortUrlVisits} goBack={goBack} />
|
<ShortUrlVisitsHeader shortUrlDetail={shortUrlDetail} shortUrlVisits={shortUrlVisits} goBack={goBack} />
|
||||||
</VisitsStats>
|
</VisitsStats>
|
||||||
);
|
);
|
||||||
}, ({ match }) => `https://shlink.io/new-visit/${match.params.shortCode}`);
|
}, ({ match }) => [ Topics.shortUrlVisits(match.params.shortCode) ]);
|
||||||
|
|
||||||
export default ShortUrlVisits;
|
export default ShortUrlVisits;
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { RouteComponentProps } from 'react-router';
|
||||||
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
||||||
import ColorGenerator from '../utils/services/ColorGenerator';
|
import ColorGenerator from '../utils/services/ColorGenerator';
|
||||||
import { ShlinkVisitsParams } from '../api/types';
|
import { ShlinkVisitsParams } from '../api/types';
|
||||||
|
import { Topics } from '../mercure/helpers/Topics';
|
||||||
import { TagVisits as TagVisitsState } from './reducers/tagVisits';
|
import { TagVisits as TagVisitsState } from './reducers/tagVisits';
|
||||||
import TagVisitsHeader from './TagVisitsHeader';
|
import TagVisitsHeader from './TagVisitsHeader';
|
||||||
import VisitsStats from './VisitsStats';
|
import VisitsStats from './VisitsStats';
|
||||||
|
@ -27,6 +28,6 @@ const TagVisits = (colorGenerator: ColorGenerator) => boundToMercureHub(({
|
||||||
<TagVisitsHeader tagVisits={tagVisits} goBack={goBack} colorGenerator={colorGenerator} />
|
<TagVisitsHeader tagVisits={tagVisits} goBack={goBack} colorGenerator={colorGenerator} />
|
||||||
</VisitsStats>
|
</VisitsStats>
|
||||||
);
|
);
|
||||||
}, () => 'https://shlink.io/new-visit');
|
}, () => [ Topics.visits() ]);
|
||||||
|
|
||||||
export default TagVisits;
|
export default TagVisits;
|
||||||
|
|
|
@ -20,7 +20,7 @@ describe('helpers', () => {
|
||||||
[ Mock.of<MercureInfo>({ loading: false, error: false, mercureHubUrl: undefined }) ],
|
[ Mock.of<MercureInfo>({ loading: false, error: false, mercureHubUrl: undefined }) ],
|
||||||
[ Mock.of<MercureInfo>({ loading: true, error: true, mercureHubUrl: undefined }) ],
|
[ Mock.of<MercureInfo>({ loading: true, error: true, mercureHubUrl: undefined }) ],
|
||||||
])('does not bind an EventSource when loading, error or no hub URL', (mercureInfo) => {
|
])('does not bind an EventSource when loading, error or no hub URL', (mercureInfo) => {
|
||||||
bindToMercureTopic(mercureInfo, '', identity, identity);
|
bindToMercureTopic(mercureInfo, [ '' ], identity, identity);
|
||||||
|
|
||||||
expect(EventSource).not.toHaveBeenCalled();
|
expect(EventSource).not.toHaveBeenCalled();
|
||||||
expect(onMessage).not.toHaveBeenCalled();
|
expect(onMessage).not.toHaveBeenCalled();
|
||||||
|
@ -40,7 +40,7 @@ describe('helpers', () => {
|
||||||
error: false,
|
error: false,
|
||||||
mercureHubUrl,
|
mercureHubUrl,
|
||||||
token,
|
token,
|
||||||
}, topic, onMessage, onTokenExpired);
|
}, [ topic ], onMessage, onTokenExpired);
|
||||||
|
|
||||||
expect(EventSource).toHaveBeenCalledWith(hubUrl, {
|
expect(EventSource).toHaveBeenCalledWith(hubUrl, {
|
||||||
headers: {
|
headers: {
|
||||||
|
|
Loading…
Reference in a new issue