diff --git a/app/javascript/chat/__tests__/util.test.js b/app/javascript/chat/__tests__/util.test.js
new file mode 100644
index 000000000..34aeca376
--- /dev/null
+++ b/app/javascript/chat/__tests__/util.test.js
@@ -0,0 +1,109 @@
+import { getUserDataAndCsrfToken } from '../util';
+
+const ERROR_MESSAGE = "Couldn't find user data on page.";
+
+describe('Chat utilities', () => {
+ describe('getUserDataAndCsrfToken', () => {
+ afterEach(() => {
+ document.head.innerHTML = '';
+ document.body.removeAttribute('data-user');
+ });
+
+ test('should reject if no user or csrf token found.', async () => {
+ await expect(getUserDataAndCsrfToken(document)).rejects.toThrow(
+ ERROR_MESSAGE,
+ );
+ });
+
+ test('should reject if csrf token found but no user.', async () => {
+ document.head.innerHTML =
+ '';
+ await expect(getUserDataAndCsrfToken(document)).rejects.toThrow(
+ ERROR_MESSAGE,
+ );
+ });
+
+ test('should reject if user found but no csrf token found.', async () => {
+ document.body.setAttribute('data-user', '{}');
+ await expect(getUserDataAndCsrfToken(document)).rejects.toThrow(
+ ERROR_MESSAGE,
+ );
+ });
+
+ test('should resolve if user and csrf token found.', () => {
+ const csrfToken = 'some-csrf-token';
+ const currentUser = {
+ id: 41,
+ name: 'Guy Fieri',
+ username: 'guyfieri',
+ profile_image_90:
+ '/uploads/user/profile_image/41/0841dbe2-208c-4daa-b498-b2f01f3d37b2.png',
+ followed_tag_names: [],
+ followed_tags: '[]',
+ followed_user_ids: [
+ 2,
+ 31,
+ 7,
+ 38,
+ 22,
+ 37,
+ 14,
+ 26,
+ 20,
+ 24,
+ 11,
+ 27,
+ 29,
+ 3,
+ 6,
+ 28,
+ 4,
+ 39,
+ 8,
+ 40,
+ 25,
+ 30,
+ 35,
+ 34,
+ 5,
+ 12,
+ 33,
+ 36,
+ 21,
+ 18,
+ 23,
+ 1,
+ 32,
+ 19,
+ 15,
+ 13,
+ 16,
+ 9,
+ 10,
+ 17,
+ ],
+ reading_list_ids: [48, 49, 34, 51, 64, 56],
+ saw_onboarding: true,
+ onboarding_checklist: {
+ write_your_first_article: false,
+ follow_your_first_tag: false,
+ fill_out_your_profile: false,
+ leave_your_first_reaction: true,
+ follow_your_first_dev: true,
+ leave_your_first_comment: false,
+ },
+ checked_code_of_conduct: false,
+ number_of_comments: 0,
+ display_sponsors: true,
+ trusted: false,
+ };
+ document.head.innerHTML = ``;
+ document.body.setAttribute('data-user', JSON.stringify(currentUser));
+
+ expect(getUserDataAndCsrfToken(document)).resolves.toEqual({
+ currentUser,
+ csrfToken,
+ });
+ });
+ });
+});
diff --git a/app/javascript/chat/util.js b/app/javascript/chat/util.js
index 4d508daf0..f21bbfd5f 100644
--- a/app/javascript/chat/util.js
+++ b/app/javascript/chat/util.js
@@ -1,35 +1,40 @@
import 'intersection-observer';
import { sendKeys } from './actions';
+export function getCsrfToken() {
+ const element = document.querySelector(`meta[name='csrf-token']`);
+
+ return element !== null ? element.content : undefined;
+}
+
+const getWaitOnUserDataHandler = ({ resolve, reject, waitTime = 20 }) => {
+ let totalTimeWaiting = 0;
+
+ return function waitingOnUserData() {
+ if (totalTimeWaiting === 3000) {
+ reject(new Error("Couldn't find user data on page."));
+ return;
+ }
+
+ const csrfToken = getCsrfToken(document);
+ const { user } = document.body.dataset;
+
+ if (user && csrfToken !== undefined) {
+ const currentUser = JSON.parse(user);
+
+ resolve({ currentUser, csrfToken });
+ return;
+ }
+
+ totalTimeWaiting += waitTime;
+ setTimeout(waitingOnUserData, waitTime);
+ };
+};
export function getUserDataAndCsrfToken() {
- const promise = new Promise((resolve, reject) => {
- let i = 0;
- const waitingOnUserData = setInterval(() => {
- let userData = null;
- const dataUserAttribute = document.body.getAttribute('data-user');
- const meta = document.querySelector("meta[name='csrf-token']");
- if (
- dataUserAttribute &&
- dataUserAttribute !== 'undefined' &&
- dataUserAttribute !== undefined &&
- meta &&
- meta.content !== 'undefined' &&
- meta.content !== undefined
- ) {
- userData = JSON.parse(dataUserAttribute);
- }
- i += 1;
- if (userData) {
- clearInterval(waitingOnUserData);
- resolve(userData);
- } else if (i === 3000) {
- clearInterval(waitingOnUserData);
- reject(new Error("Couldn't find user data on page."));
- }
- }, 5);
+ return new Promise((resolve, reject) => {
+ getWaitOnUserDataHandler({ resolve, reject })();
});
- return promise;
}
export function scrollToBottom() {
@@ -76,39 +81,41 @@ export function adjustTimestamp(timestamp) {
return time;
}
-
export function setupNotifications() {
- navigator.serviceWorker.ready.then((serviceWorkerRegistration) => {
- serviceWorkerRegistration.pushManager.getSubscription()
- .then(function(subscription) {
+ navigator.serviceWorker.ready.then(serviceWorkerRegistration => {
+ serviceWorkerRegistration.pushManager
+ .getSubscription()
+ .then(subscription => {
if (subscription) {
return subscription;
}
return serviceWorkerRegistration.pushManager.subscribe({
userVisibleOnly: true,
- applicationServerKey: window.vapidPublicKey
+ applicationServerKey: window.vapidPublicKey,
});
- }).then(function(subscription) {
- sendKeys(subscription.toJSON(), null, null)
+ })
+ .then(subscription => {
+ sendKeys(subscription.toJSON(), null, null);
});
});
}
export function getNotificationState() {
- //Not yet ready
- if (!window.location.href.includes('ask-for-notifications')){
- return "dont-ask"
+ // Not yet ready
+ if (!window.location.href.includes('ask-for-notifications')) {
+ return 'dont-ask';
}
// Let's check if the browser supports notifications
- if (!("Notification" in window)) {
- return "not-supported"
- } else if (Notification.permission === "granted") {
- setupNotifications();
- return "granted"
- } else if (Notification.permission !== 'denied') {
- return "waiting-permission"
- } else {
- return "denied"
+ if (!('Notification' in window)) {
+ return 'not-supported';
}
-}
\ No newline at end of file
+
+ const { permission } = Notification;
+
+ if (permission === 'granted') {
+ setupNotifications();
+ }
+
+ return permission === 'default' ? 'waiting-permission' : permission;
+}
diff --git a/app/javascript/packs/Chat.jsx b/app/javascript/packs/Chat.jsx
new file mode 100644
index 000000000..a9c652c92
--- /dev/null
+++ b/app/javascript/packs/Chat.jsx
@@ -0,0 +1,49 @@
+import { h, render } from 'preact';
+import { getUserDataAndCsrfToken } from '../chat/util';
+import Chat from '../chat/chat';
+
+function initializeChat(loadChat) {
+ getUserDataAndCsrfToken()
+ .then(loadChat)
+ .catch(error => {
+ // eslint-disable-next-line no-console
+ console.error('Unable to load chat', error);
+ });
+}
+
+function renderChat(root) {
+ render(