This repository was archived by the owner on Nov 13, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 62
/
Copy pathuse-messages.ts
116 lines (99 loc) · 3.65 KB
/
use-messages.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
// This file is unused so there is no need to keep it ts-checked
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
import { useState, useEffect, useRef } from "react";
import { FetchMessagesParameters, MessageActionEvent, MessageEvent } from "pubnub";
import { usePubNub } from "pubnub-react";
import { MessageEnvelope } from "../types";
import { merge, mergeWith, cloneDeep, set as setDeep } from "lodash";
interface MessagesByChannel {
[channel: string]: MessageEnvelope[];
}
type HookReturnValue = [MessagesByChannel, () => Promise<void>, Error];
export const useMessages = (options: FetchMessagesParameters): HookReturnValue => {
const pubnub = usePubNub();
const [messages, setMessages] = useState<MessagesByChannel>(() => {
const initial = {};
options.channels.forEach((channel) => (initial[channel] = []));
return initial;
});
const [page, setPage] = useState<number>(undefined);
const [fetchedAll, setFetchedAll] = useState(false);
const [error, setError] = useState<Error>();
const mandatoryOptions = {
start: page,
};
const mergedOptions = merge(options, mandatoryOptions);
const command = async () => {
try {
if (fetchedAll) return;
const response = await pubnub.fetchMessages(mergedOptions);
const newMessages = mergeWith({}, messages, response.channels, mergeMessageArray);
const earliestMessageTimetokens = Object.values(response.channels)
.flatMap((ary) => ary[0])
.map((a) => a.timetoken) as number[];
const lastTimetoken = Math.min(...earliestMessageTimetokens);
setMessages(newMessages);
setPage(lastTimetoken - 1);
setFetchedAll(!Object.keys(response.channels).length);
} catch (e) {
setError(e);
}
};
const handleMessage = (message: MessageEvent) => {
try {
setMessages((messages) => {
const messagesClone = cloneDeep(messages);
if (!messagesClone[message.channel]) messagesClone[message.channel] = [];
messagesClone[message.channel].push(message);
return messagesClone;
});
} catch (e) {
setError(e);
}
};
const handleAction = (action: MessageActionEvent) => {
try {
if (!messages[action.channel]) return;
setMessages((messages) => {
const { channel, event } = action;
const { type, value, actionTimetoken, messageTimetoken, uuid } = action.data;
const messagesClone = cloneDeep(messages);
const message = messagesClone[channel].find((m) => m.timetoken === messageTimetoken);
const actions = message?.actions?.[type]?.[value] || [];
if (message && event === "added") {
const newActions = [...actions, { uuid, actionTimetoken }];
setDeep(message, ["actions", type, value], newActions);
}
if (message && event === "removed") {
const newActions = actions.filter((a) => a.actionTimetoken !== actionTimetoken);
newActions.length
? setDeep(message, ["actions", type, value], newActions)
: delete message.actions[type][value];
}
return messagesClone;
});
} catch (e) {
setError(e);
}
};
const listener = useRef({
message: handleMessage,
messageAction: handleAction,
});
useEffect(() => {
command();
pubnub.addListener(listener.current);
return () => {
pubnub.removeListener(listener.current);
};
}, [pubnub]);
return [messages, command, error];
};
const mergeMessageArray = (oldMessages, newMessages) => {
if (Array.isArray(oldMessages)) {
return [...oldMessages, ...newMessages].sort(
(a, b) => (a.timetoken as number) - (b.timetoken as number)
);
}
};