customerio-gist-web 0.0.0-test-oidc
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.
- package/.github/workflows/eslint.yml +26 -0
- package/.github/workflows/release.yml +46 -0
- package/.github/workflows/release_hotfix.yml +41 -0
- package/.github/workflows/release_version.yml +57 -0
- package/.mise/config.toml +2 -0
- package/CODEOWNERS +2 -0
- package/LICENSE +21 -0
- package/README.md +29 -0
- package/dist/gist.min.js +1 -0
- package/eslint.config.mjs +10 -0
- package/examples/index.html +126 -0
- package/examples/styles.css +55 -0
- package/index.d.ts +1 -0
- package/index.js +2 -0
- package/package.json +49 -0
- package/src/gist.js +128 -0
- package/src/index.js +2 -0
- package/src/managers/custom-attribute-manager.js +82 -0
- package/src/managers/gist-properties-manager.js +36 -0
- package/src/managers/locale-manager.js +16 -0
- package/src/managers/message-broadcast-manager.js +107 -0
- package/src/managers/message-component-manager.js +192 -0
- package/src/managers/message-manager.js +314 -0
- package/src/managers/message-user-queue-manager.js +80 -0
- package/src/managers/page-component-manager.js +19 -0
- package/src/managers/queue-manager.js +216 -0
- package/src/managers/user-manager.js +88 -0
- package/src/services/log-service.js +19 -0
- package/src/services/network.js +81 -0
- package/src/services/queue-service.js +75 -0
- package/src/services/settings.js +65 -0
- package/src/templates/embed.js +69 -0
- package/src/templates/message.js +57 -0
- package/src/utilities/event-emitter.js +12 -0
- package/src/utilities/local-storage.js +86 -0
- package/src/utilities/log.js +7 -0
- package/src/utilities/preview-mode.js +20 -0
- package/test.sh +4 -0
- package/webpack.config.js +11 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import Gist from '../gist';
|
|
2
|
+
import { log } from "../utilities/log";
|
|
3
|
+
import { logMessageView, logUserMessageView } from "../services/log-service";
|
|
4
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
5
|
+
import { settings } from "../services/settings";
|
|
6
|
+
import {
|
|
7
|
+
loadOverlayComponent,
|
|
8
|
+
showOverlayComponent,
|
|
9
|
+
hideOverlayComponent,
|
|
10
|
+
removeOverlayComponent,
|
|
11
|
+
loadEmbedComponent,
|
|
12
|
+
showEmbedComponent,
|
|
13
|
+
hideEmbedComponent,
|
|
14
|
+
resizeComponent,
|
|
15
|
+
elementHasHeight,
|
|
16
|
+
isElementLoaded,
|
|
17
|
+
changeOverlayTitle
|
|
18
|
+
} from "./message-component-manager";
|
|
19
|
+
import { resolveMessageProperties } from "./gist-properties-manager";
|
|
20
|
+
import { positions, addPageElement } from "./page-component-manager";
|
|
21
|
+
import { getAllCustomAttributes } from "./custom-attribute-manager";
|
|
22
|
+
import { checkMessageQueue } from "./queue-manager";
|
|
23
|
+
import { isMessageBroadcast, markBroadcastAsSeen, markBroadcastAsDismissed } from './message-broadcast-manager';
|
|
24
|
+
import { markUserQueueMessageAsSeen } from './message-user-queue-manager';
|
|
25
|
+
import { setMessageLoaded } from './message-user-queue-manager';
|
|
26
|
+
|
|
27
|
+
export async function showMessage(message) {
|
|
28
|
+
if (Gist.isDocumentVisible) {
|
|
29
|
+
if (Gist.overlayInstanceId) {
|
|
30
|
+
log(`Message ${Gist.overlayInstanceId} already showing.`);
|
|
31
|
+
return null;
|
|
32
|
+
} else {
|
|
33
|
+
var properties = resolveMessageProperties(message)
|
|
34
|
+
message.instanceId = uuidv4();
|
|
35
|
+
message.overlay = true;
|
|
36
|
+
message.firstLoad = true;
|
|
37
|
+
message.shouldResizeHeight = true;
|
|
38
|
+
message.shouldScale = properties.shouldScale;
|
|
39
|
+
message.renderStartTime = new Date().getTime();
|
|
40
|
+
Gist.overlayInstanceId = message.instanceId;
|
|
41
|
+
Gist.currentMessages.push(message);
|
|
42
|
+
|
|
43
|
+
return loadMessageComponent(message);
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
log("Document hidden, not showing message now.");
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function embedMessage(message, elementId) {
|
|
52
|
+
if (Gist.isDocumentVisible) {
|
|
53
|
+
message.instanceId = uuidv4();
|
|
54
|
+
message.overlay = false;
|
|
55
|
+
message.firstLoad = true;
|
|
56
|
+
message.shouldScale = false;
|
|
57
|
+
message.elementId = elementId;
|
|
58
|
+
message.shouldResizeHeight = !elementHasHeight(elementId);
|
|
59
|
+
message.renderStartTime = new Date().getTime();
|
|
60
|
+
Gist.currentMessages.push(message);
|
|
61
|
+
|
|
62
|
+
return loadMessageComponent(message, elementId);
|
|
63
|
+
} else {
|
|
64
|
+
log("Document hidden, not showing message now.");
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function hideMessage(message) {
|
|
70
|
+
if (message) {
|
|
71
|
+
Gist.messageDismissed(message);
|
|
72
|
+
|
|
73
|
+
if (message.overlay) {
|
|
74
|
+
await resetOverlayState(true, message);
|
|
75
|
+
} else {
|
|
76
|
+
resetEmbedState(message);
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
log(`Message with instance id: ${message.instanceId} not found`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function removePersistentMessage(message) {
|
|
84
|
+
var messageProperties = resolveMessageProperties(message);
|
|
85
|
+
if (message) {
|
|
86
|
+
if (messageProperties.persistent) {
|
|
87
|
+
log(`Persistent message dismissed, logging view`);
|
|
88
|
+
await logUserMessageViewLocally(message);
|
|
89
|
+
await reportMessageView(message);
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
log(`Message with instance id: ${message.instanceId} not found`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function resetEmbedState(message) {
|
|
97
|
+
removeMessageByInstanceId(message.instanceId);
|
|
98
|
+
hideEmbedComponent(message.elementId);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function resetOverlayState(hideFirst, message) {
|
|
102
|
+
if (hideFirst) {
|
|
103
|
+
await hideOverlayComponent();
|
|
104
|
+
} else {
|
|
105
|
+
removeOverlayComponent();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (Gist.currentMessages.length == 0) {
|
|
109
|
+
window.removeEventListener('message', handleGistEvents);
|
|
110
|
+
window.removeEventListener('touchstart', handleTouchStartEvents);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
removeMessageByInstanceId(message.instanceId);
|
|
114
|
+
Gist.overlayInstanceId = null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function loadMessageComponent(message, elementId = null) {
|
|
118
|
+
if (elementId && isElementLoaded(elementId)) {
|
|
119
|
+
log(`Message ${message.messageId} already showing in element ${elementId}.`);
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
var options = {
|
|
124
|
+
endpoint: settings.ENGINE_API_ENDPOINT[Gist.config.env],
|
|
125
|
+
siteId: Gist.config.siteId,
|
|
126
|
+
dataCenter: Gist.config.dataCenter,
|
|
127
|
+
messageId: message.messageId,
|
|
128
|
+
instanceId: message.instanceId,
|
|
129
|
+
livePreview: false,
|
|
130
|
+
properties: message.properties,
|
|
131
|
+
customAttributes: Object.fromEntries(getAllCustomAttributes())
|
|
132
|
+
}
|
|
133
|
+
var url = `${settings.GIST_VIEW_ENDPOINT[Gist.config.env]}/index.html`
|
|
134
|
+
window.addEventListener('message', handleGistEvents);
|
|
135
|
+
window.addEventListener('touchstart', handleTouchStartEvents);
|
|
136
|
+
|
|
137
|
+
if (elementId) {
|
|
138
|
+
if (positions.includes(elementId)) { addPageElement(elementId); }
|
|
139
|
+
loadEmbedComponent(elementId, url, message, options);
|
|
140
|
+
} else {
|
|
141
|
+
loadOverlayComponent(url, message, options);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return message;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
async function reportMessageView(message) {
|
|
149
|
+
log(`Message shown, logging view for: ${message.messageId}`);
|
|
150
|
+
var response = {};
|
|
151
|
+
if (message.queueId != null) {
|
|
152
|
+
await logUserMessageViewLocally(message);
|
|
153
|
+
response = await logUserMessageView(message.queueId);
|
|
154
|
+
} else {
|
|
155
|
+
response = await logMessageView(message.messageId);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (response.status === 200) {
|
|
159
|
+
log(`Message view logged`);
|
|
160
|
+
} else {
|
|
161
|
+
log(`Problem logging message: ${response.status}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function fetchMessageByInstanceId(instanceId) {
|
|
166
|
+
return Gist.currentMessages.find(message => message.instanceId === instanceId);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function removeMessageByInstanceId(instanceId) {
|
|
170
|
+
Gist.currentMessages = Gist.currentMessages.filter(message => message.instanceId !== instanceId)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function updateMessageByInstanceId(instanceId, message) {
|
|
174
|
+
removeMessageByInstanceId(instanceId);
|
|
175
|
+
Gist.currentMessages.push(message);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
function handleTouchStartEvents() {
|
|
180
|
+
// Added this to avoid errors in the console
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function handleGistEvents(e) {
|
|
184
|
+
if (e.data.gist && e.origin === settings.RENDERER_HOST) {
|
|
185
|
+
var currentInstanceId = e.data.gist.instanceId;
|
|
186
|
+
var currentMessage = fetchMessageByInstanceId(currentInstanceId);
|
|
187
|
+
if (!currentMessage) { return; }
|
|
188
|
+
var messageProperties = resolveMessageProperties(currentMessage);
|
|
189
|
+
switch (e.data.gist.method) {
|
|
190
|
+
case "routeLoaded": {
|
|
191
|
+
var timeElapsed = (new Date().getTime() - currentMessage.renderStartTime) * 0.001;
|
|
192
|
+
log(`Engine render for message: ${currentMessage.messageId} timer elapsed in ${timeElapsed.toFixed(3)} seconds`);
|
|
193
|
+
setMessageLoaded(currentMessage.queueId);
|
|
194
|
+
currentMessage.currentRoute = e.data.gist.parameters.route;
|
|
195
|
+
if (currentMessage.firstLoad) {
|
|
196
|
+
if (currentMessage.overlay) {
|
|
197
|
+
showOverlayComponent(currentMessage);
|
|
198
|
+
} else {
|
|
199
|
+
showEmbedComponent(currentMessage.elementId);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
Gist.messageShown(currentMessage);
|
|
203
|
+
if (messageProperties.persistent) {
|
|
204
|
+
log(`Persistent message shown, skipping logging view`);
|
|
205
|
+
} else {
|
|
206
|
+
await reportMessageView(currentMessage);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
currentMessage.firstLoad = false;
|
|
210
|
+
}
|
|
211
|
+
updateMessageByInstanceId(currentInstanceId, currentMessage);
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
case "tap": {
|
|
215
|
+
var action = e.data.gist.parameters.action;
|
|
216
|
+
var name = e.data.gist.parameters.name;
|
|
217
|
+
Gist.messageAction(currentMessage, action, name);
|
|
218
|
+
|
|
219
|
+
if (e.data.gist.parameters.system && !messageProperties.persistent) {
|
|
220
|
+
await hideMessage(currentMessage);
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
var url = new URL(action);
|
|
226
|
+
if (url && url.protocol === "gist:") {
|
|
227
|
+
var gistAction = url.href.replace("gist://", "").split('?')[0];
|
|
228
|
+
switch (gistAction) {
|
|
229
|
+
case "close":
|
|
230
|
+
await hideMessage(currentMessage);
|
|
231
|
+
await removePersistentMessage(currentMessage);
|
|
232
|
+
await logBroadcastDismissedLocally(currentMessage);
|
|
233
|
+
await checkMessageQueue();
|
|
234
|
+
break;
|
|
235
|
+
case "showMessage":
|
|
236
|
+
var messageId = url.searchParams.get('messageId');
|
|
237
|
+
var properties = url.searchParams.get('properties');
|
|
238
|
+
if (messageId) {
|
|
239
|
+
if (properties) {
|
|
240
|
+
properties = JSON.parse(atob(properties));
|
|
241
|
+
}
|
|
242
|
+
await Gist.showMessage({ messageId: messageId, properties: properties });
|
|
243
|
+
}
|
|
244
|
+
break;
|
|
245
|
+
case "loadPage":
|
|
246
|
+
url = url.href.substring(url.href.indexOf('?url=') + 5);
|
|
247
|
+
if (url) {
|
|
248
|
+
if (url.startsWith("mailto:") || url.startsWith("https://") || url.startsWith("http://") || url.startsWith("/")) {
|
|
249
|
+
window.location.href = url;
|
|
250
|
+
} else {
|
|
251
|
+
window.location.href = window.location + url;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
} catch {
|
|
258
|
+
// If the action is not a URL, we don't need to do anything.
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
case "routeChanged": {
|
|
264
|
+
currentMessage.currentRoute = e.data.gist.parameters.route;
|
|
265
|
+
currentMessage.renderStartTime = new Date().getTime();
|
|
266
|
+
updateMessageByInstanceId(currentInstanceId, currentMessage);
|
|
267
|
+
log(`Route changed to: ${currentMessage.currentRoute}`);
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
case "sizeChanged": {
|
|
271
|
+
log(`Size Changed Width: ${e.data.gist.parameters.width} - Height: ${e.data.gist.parameters.height}`);
|
|
272
|
+
if (!currentMessage.elementId || currentMessage.shouldResizeHeight) {
|
|
273
|
+
resizeComponent(currentMessage, e.data.gist.parameters);
|
|
274
|
+
}
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
case "titleChanged": {
|
|
278
|
+
log(`Overlay title changed to: ${e.data.gist.parameters.title}`);
|
|
279
|
+
changeOverlayTitle(currentInstanceId, e.data.gist.parameters.title);
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
case "eventDispatched": {
|
|
283
|
+
Gist.events.dispatch("eventDispatched", { "name": e.data.gist.parameters.name, "payload": e.data.gist.parameters.payload });
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
case "error":
|
|
287
|
+
case "routeError": {
|
|
288
|
+
Gist.messageError(currentMessage);
|
|
289
|
+
if (Gist.overlayInstanceId) {
|
|
290
|
+
resetOverlayState(false, currentMessage);
|
|
291
|
+
} else {
|
|
292
|
+
resetEmbedState(currentMessage);
|
|
293
|
+
}
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async function logUserMessageViewLocally(message) {
|
|
301
|
+
log(`Logging user message view locally for: ${message.queueId}`);
|
|
302
|
+
if (isMessageBroadcast(message)) {
|
|
303
|
+
await markBroadcastAsSeen(message.queueId);
|
|
304
|
+
} else {
|
|
305
|
+
await markUserQueueMessageAsSeen(message.queueId);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export async function logBroadcastDismissedLocally(message) {
|
|
310
|
+
if (isMessageBroadcast(message)) {
|
|
311
|
+
log(`Logging broadcast dismissed locally for: ${message.queueId}`);
|
|
312
|
+
await markBroadcastAsDismissed(message.queueId);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { getKeyFromLocalStore, setKeyToLocalStore, clearKeyFromLocalStore } from '../utilities/local-storage';
|
|
2
|
+
import { getHashedUserToken } from './user-manager';
|
|
3
|
+
|
|
4
|
+
const messageQueueLocalStoreName = "gist.web.message.user";
|
|
5
|
+
const messagesLocalStoreCacheInMinutes = 60;
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export async function updateQueueLocalStore(messages) {
|
|
9
|
+
const userQueueLocalStoreName = await getUserQueueLocalStoreName();
|
|
10
|
+
if (!userQueueLocalStoreName) return;
|
|
11
|
+
|
|
12
|
+
const nonBroadcasts = messages.filter(message =>
|
|
13
|
+
!(message.properties && message.properties.gist && message.properties.gist.broadcast)
|
|
14
|
+
);
|
|
15
|
+
const expiryDate = new Date();
|
|
16
|
+
expiryDate.setMinutes(expiryDate.getMinutes() + messagesLocalStoreCacheInMinutes);
|
|
17
|
+
setKeyToLocalStore(userQueueLocalStoreName, nonBroadcasts, expiryDate);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function getMessagesFromLocalStore() {
|
|
21
|
+
const userQueueLocalStoreName = await getUserQueueLocalStoreName();
|
|
22
|
+
if (!userQueueLocalStoreName) return [];
|
|
23
|
+
|
|
24
|
+
const storedMessages = getKeyFromLocalStore(userQueueLocalStoreName) ?? [];
|
|
25
|
+
const seenMessages = await getSeenMessagesFromLocalStore();
|
|
26
|
+
return storedMessages.filter(message => !seenMessages.includes(message.queueId));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function markUserQueueMessageAsSeen(queueId) {
|
|
30
|
+
const userSeenQueueLocalStoreName = await getUserSeenQueueLocalStoreName();
|
|
31
|
+
if (!userSeenQueueLocalStoreName) return;
|
|
32
|
+
|
|
33
|
+
const seenMessages = getKeyFromLocalStore(userSeenQueueLocalStoreName) ?? [];
|
|
34
|
+
seenMessages.push(queueId);
|
|
35
|
+
setKeyToLocalStore(userSeenQueueLocalStoreName, seenMessages);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function isMessageLoading(queueId) {
|
|
39
|
+
const messageLoadingLocalStoreName = await getMessageLoadingStateLocalStoreName(queueId);
|
|
40
|
+
if (!messageLoadingLocalStoreName) return false;
|
|
41
|
+
return getKeyFromLocalStore(messageLoadingLocalStoreName) !== null ? true : false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function setMessageLoading(queueId) {
|
|
45
|
+
const messageLoadingLocalStoreName = await getMessageLoadingStateLocalStoreName(queueId);
|
|
46
|
+
if (!messageLoadingLocalStoreName) return false;
|
|
47
|
+
// We add a TTL of 5sec, just in case the message gets stuck loading.
|
|
48
|
+
setKeyToLocalStore(messageLoadingLocalStoreName, true, new Date(Date.now() + 5000));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function setMessageLoaded(queueId) {
|
|
52
|
+
const messageLoadingLocalStoreName = await getMessageLoadingStateLocalStoreName(queueId);
|
|
53
|
+
if (!messageLoadingLocalStoreName) return false;
|
|
54
|
+
clearKeyFromLocalStore(messageLoadingLocalStoreName);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function getSeenMessagesFromLocalStore() {
|
|
58
|
+
const userSeenQueueLocalStoreName = await getUserSeenQueueLocalStoreName();
|
|
59
|
+
if (!userSeenQueueLocalStoreName) return [];
|
|
60
|
+
|
|
61
|
+
return getKeyFromLocalStore(userSeenQueueLocalStoreName) ?? [];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function getUserQueueLocalStoreName() {
|
|
65
|
+
const userToken = await getHashedUserToken();
|
|
66
|
+
if (!userToken) return null;
|
|
67
|
+
return `${messageQueueLocalStoreName}.${userToken}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function getUserSeenQueueLocalStoreName() {
|
|
71
|
+
const userToken = await getHashedUserToken();
|
|
72
|
+
if (!userToken) return null;
|
|
73
|
+
return `${messageQueueLocalStoreName}.${userToken}.seen`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function getMessageLoadingStateLocalStoreName(queueId) {
|
|
77
|
+
const userToken = await getHashedUserToken();
|
|
78
|
+
if (!userToken) return null;
|
|
79
|
+
return `${messageQueueLocalStoreName}.${userToken}.message.${queueId}.loading`
|
|
80
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { log } from "../utilities/log";
|
|
2
|
+
|
|
3
|
+
export var positions = ["x-gist-top", "x-gist-floating-top", "x-gist-bottom", "x-gist-floating-bottom", "x-gist-floating-bottom-left", "x-gist-floating-bottom-right", "x-gist-floating-top-left", "x-gist-floating-top-right"];
|
|
4
|
+
|
|
5
|
+
export function addPageElement(position) {
|
|
6
|
+
const element = document.createElement("div");
|
|
7
|
+
element.id = position
|
|
8
|
+
|
|
9
|
+
switch (position) {
|
|
10
|
+
case "x-gist-top":
|
|
11
|
+
document.body.insertBefore(element, document.body.firstChild);
|
|
12
|
+
break;
|
|
13
|
+
default:
|
|
14
|
+
document.body.insertAdjacentElement("beforeend", element);
|
|
15
|
+
break;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
log('Top & bottom elements injected into page');
|
|
19
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import Gist from '../gist';
|
|
2
|
+
import { log } from "../utilities/log";
|
|
3
|
+
import { getUserToken, isAnonymousUser } from "./user-manager";
|
|
4
|
+
import { getUserQueue, getQueueSSEEndpoint, userQueueNextPullCheckLocalStoreName } from "../services/queue-service";
|
|
5
|
+
import { showMessage, embedMessage } from "./message-manager";
|
|
6
|
+
import { resolveMessageProperties } from "./gist-properties-manager";
|
|
7
|
+
import { clearKeyFromLocalStore, getKeyFromLocalStore } from '../utilities/local-storage';
|
|
8
|
+
import { updateBroadcastsLocalStore, getEligibleBroadcasts, isShowAlwaysBroadcast } from './message-broadcast-manager';
|
|
9
|
+
import { updateQueueLocalStore, getMessagesFromLocalStore, isMessageLoading, setMessageLoading } from './message-user-queue-manager';
|
|
10
|
+
import { settings } from '../services/settings';
|
|
11
|
+
|
|
12
|
+
var sleep = time => new Promise(resolve => setTimeout(resolve, time))
|
|
13
|
+
var poll = (promiseFn, time) => promiseFn().then(sleep(time).then(() => poll(promiseFn, time)));
|
|
14
|
+
var pollingSetup = false;
|
|
15
|
+
let sseSource = null;
|
|
16
|
+
|
|
17
|
+
export async function startQueueListener() {
|
|
18
|
+
if (!pollingSetup) {
|
|
19
|
+
if (getUserToken()) {
|
|
20
|
+
log("Queue watcher started");
|
|
21
|
+
pollingSetup = true;
|
|
22
|
+
poll(() => new Promise(() => pullMessagesFromQueue()), 1000);
|
|
23
|
+
} else {
|
|
24
|
+
log("User token not setup, queue not started.");
|
|
25
|
+
}
|
|
26
|
+
} else {
|
|
27
|
+
await checkMessageQueue();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function checkMessageQueue() {
|
|
32
|
+
var broadcastMessages = await getEligibleBroadcasts();
|
|
33
|
+
var userMessages = await getMessagesFromLocalStore();
|
|
34
|
+
var allMessages = broadcastMessages.concat(userMessages);
|
|
35
|
+
|
|
36
|
+
log(`Messages in local queue: ${allMessages.length}`);
|
|
37
|
+
var orderedMessages = allMessages.sort((a, b) => a.priority - b.priority);
|
|
38
|
+
for (const message of orderedMessages) {
|
|
39
|
+
await handleMessage(message);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
//TODO: Move this to a utility and only return valid messages (from: getEligibleBroadcasts getMessagesFromLocalStore) & to handleMessage
|
|
44
|
+
async function handleMessage(message) {
|
|
45
|
+
var messageProperties = resolveMessageProperties(message);
|
|
46
|
+
if (messageProperties.hasRouteRule) {
|
|
47
|
+
var currentUrl = Gist.currentRoute;
|
|
48
|
+
if (currentUrl == null) {
|
|
49
|
+
currentUrl = new URL(window.location.href).pathname;
|
|
50
|
+
}
|
|
51
|
+
var routeRule = messageProperties.routeRule;
|
|
52
|
+
log(`Verifying route ${currentUrl} against rule: ${routeRule}`);
|
|
53
|
+
var urlTester = new RegExp(routeRule);
|
|
54
|
+
if (!urlTester.test(currentUrl)) {
|
|
55
|
+
log(`Route ${currentUrl} does not match rule.`);
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (messageProperties.hasPosition) {
|
|
60
|
+
message.position = messageProperties.position;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// If the message is not persistant, is not a show always broadcast, and is already loading, we skip it.
|
|
64
|
+
if (!messageProperties.persistent && !isShowAlwaysBroadcast(message) && await isMessageLoading(message.queueId)) {
|
|
65
|
+
log(`Not showing message with queueId ${message.queueId} because its already loading.`);
|
|
66
|
+
return false;
|
|
67
|
+
} else {
|
|
68
|
+
var loading = false;
|
|
69
|
+
if (messageProperties.isEmbedded) {
|
|
70
|
+
loading = await embedMessage(message, messageProperties.elementId);
|
|
71
|
+
} else {
|
|
72
|
+
loading = await showMessage(message);
|
|
73
|
+
}
|
|
74
|
+
if (loading) setMessageLoading(message.queueId);
|
|
75
|
+
return loading;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function pullMessagesFromQueue() {
|
|
80
|
+
// If SSE connection is already active, just check the local queue
|
|
81
|
+
if (settings.hasActiveSSEConnection()) {
|
|
82
|
+
// Close our SSE connection if we're not the main instance.
|
|
83
|
+
if (!settings.isSSEConnectionManagedBySDK() && sseSource) {
|
|
84
|
+
log("Not the main instance, closing our SSE connection.");
|
|
85
|
+
stopSSEListener();
|
|
86
|
+
}
|
|
87
|
+
await checkMessageQueue();
|
|
88
|
+
return;
|
|
89
|
+
} else {
|
|
90
|
+
if (sseSource) {
|
|
91
|
+
log("SSE connection not active, closing it.");
|
|
92
|
+
stopSSEListener();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// If SSE is enabled and user is not anonymous, set up SSE listener
|
|
97
|
+
if (settings.useSSE() && !isAnonymousUser()) {
|
|
98
|
+
await setupSSEQueueListener();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Fall back to polling
|
|
103
|
+
await checkQueueThroughPolling();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function checkQueueThroughPolling() {
|
|
107
|
+
if (getUserToken()) {
|
|
108
|
+
if (Gist.isDocumentVisible) {
|
|
109
|
+
// We're using the TTL as a way to determine if we should check the queue, so if the key is not there, we check the queue.
|
|
110
|
+
if (getKeyFromLocalStore(userQueueNextPullCheckLocalStoreName) === null) {
|
|
111
|
+
var response = await getUserQueue();
|
|
112
|
+
var responseData = [];
|
|
113
|
+
if (response) {
|
|
114
|
+
if (response.status === 200 || response.status === 204) {
|
|
115
|
+
log("200 response, updating local store.");
|
|
116
|
+
responseData = response.data;
|
|
117
|
+
updateQueueLocalStore(responseData);
|
|
118
|
+
updateBroadcastsLocalStore(responseData);
|
|
119
|
+
} else if (response.status === 304) {
|
|
120
|
+
log("304 response, using local store.");
|
|
121
|
+
}
|
|
122
|
+
await checkMessageQueue();
|
|
123
|
+
} else {
|
|
124
|
+
log(`There was an error while checking message queue.`);
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
log(`Next queue pull scheduled for later.`);
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
log(`Document not visible, skipping queue check.`);
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
log(`User token reset, skipping queue check.`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function setupSSEQueueListener() {
|
|
138
|
+
// Close any existing SSE connection.
|
|
139
|
+
stopSSEListener();
|
|
140
|
+
|
|
141
|
+
// Get the SSE endpoint.
|
|
142
|
+
const sseURL = getQueueSSEEndpoint();
|
|
143
|
+
if (sseURL === null) {
|
|
144
|
+
log("SSE endpoint not available, falling back to polling.");
|
|
145
|
+
await checkQueueThroughPolling();
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
log(`Starting SSE queue listener on ${sseURL}`);
|
|
149
|
+
sseSource = new EventSource(sseURL);
|
|
150
|
+
settings.setActiveSSEConnection();
|
|
151
|
+
|
|
152
|
+
sseSource.addEventListener("connected", async (event) => {
|
|
153
|
+
try {
|
|
154
|
+
log("SSE connection received:", event);
|
|
155
|
+
settings.setUseSSEFlag(true);
|
|
156
|
+
var config = JSON.parse(event.data);
|
|
157
|
+
if(config.heartbeat) {
|
|
158
|
+
settings.setSSEHeartbeat(config.heartbeat);
|
|
159
|
+
log(`SSE heartbeat set to ${config.heartbeat} seconds`);
|
|
160
|
+
}
|
|
161
|
+
settings.setActiveSSEConnection();
|
|
162
|
+
} catch (e) {
|
|
163
|
+
log(`Failed to parse SSE settings: ${e}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// On successful SSE connection, pull the queue.
|
|
167
|
+
clearKeyFromLocalStore(userQueueNextPullCheckLocalStoreName);
|
|
168
|
+
await checkQueueThroughPolling();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
sseSource.addEventListener("messages", async (event) => {
|
|
172
|
+
try {
|
|
173
|
+
var messages = JSON.parse(event.data);
|
|
174
|
+
log("SSE message received:", messages);
|
|
175
|
+
await updateQueueLocalStore(messages);
|
|
176
|
+
await updateBroadcastsLocalStore(messages);
|
|
177
|
+
await checkMessageQueue();
|
|
178
|
+
} catch (e) {
|
|
179
|
+
log("Failed to parse SSE message", e);
|
|
180
|
+
stopSSEListener();
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
sseSource.addEventListener("error", async (event) => {
|
|
185
|
+
log("SSE error received:", event);
|
|
186
|
+
stopSSEListener();
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
sseSource.addEventListener("heartbeat", async (event) => {
|
|
190
|
+
log("SSE heartbeat received:", event);
|
|
191
|
+
settings.setActiveSSEConnection();
|
|
192
|
+
settings.setUseSSEFlag(true);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function stopSSEListener(disconnectGlobally = false) {
|
|
197
|
+
// When logging out, we need every instance to disconnect.
|
|
198
|
+
if (disconnectGlobally) {
|
|
199
|
+
settings.removeActiveSSEConnection();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Update settings to reflect disconnected state
|
|
203
|
+
if (disconnectGlobally || settings.isSSEConnectionManagedBySDK()) {
|
|
204
|
+
settings.setUseSSEFlag(false);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// No active SSE connection to stop
|
|
208
|
+
if (!sseSource) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Close the connection and clean up
|
|
213
|
+
log("Stopping SSE queue listener...");
|
|
214
|
+
sseSource.close();
|
|
215
|
+
sseSource = null;
|
|
216
|
+
}
|