Skip to content
This repository was archived by the owner on Jun 30, 2022. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/api.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import queryString from 'query-string';
import LivechatClient from '@rocket.chat/sdk/lib/clients/Livechat';

const host = window.SERVER_URL
|| queryString.parse(window.location.search).serverUrl
|| (process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : null);
const host = window.SERVER_URL
|| queryString.parse(window.location.search).serverUrl
|| (process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : null);
const useSsl = host && host.match(/^https:/) !== null;

export const Livechat = new LivechatClient({ host, protocol: 'ddp', useSsl });
18 changes: 18 additions & 0 deletions src/components/App/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Component } from 'preact';
import { Router, route } from 'preact-router';
import queryString from 'query-string';

import { Livechat } from '../../api';
import { sessionUpdate, userSessionWithoutLocation, userSessionPresence } from '../../lib/userSession';
import history from '../../history';
import Chat from '../../routes/Chat';
import LeaveMessage from '../../routes/LeaveMessage';
Expand Down Expand Up @@ -133,9 +135,25 @@ export class App extends Component {
I18n.on('change', this.handleLanguageChange);
}

async initSession() {
const { config: { settings: { allowCollectGuestLocation }, session } = {} } = this.props;
if (!session) {
if (allowCollectGuestLocation) {
sessionUpdate();
} else {
await Livechat.sendSessionData(userSessionWithoutLocation);
userSessionPresence.init();
}
} else {
// Update visit count for user
Livechat.updateVisitCount(session.token);
}
}

async initialize() {
// TODO: split these behaviors into composable components
await Connection.init();
this.initSession();
this.handleTriggers();
CustomFields.init();
Hooks.init();
Expand Down
2 changes: 1 addition & 1 deletion src/i18n/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,4 @@
"your_spot_is_spot_a35cd288": "Seu lugar é #%{spot}",
"your_spot_is_spot_estimated_wait_time_estimatedwai_d0ff46e0": "Seu lugar é #%{spot} (Tempo estimado: %{estimatedWaitTime})"
}
}
}
2 changes: 1 addition & 1 deletion src/i18n/pt_BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,4 @@
"your_spot_is_spot_a35cd288": "Seu lugar é #%{spot}",
"your_spot_is_spot_estimated_wait_time_estimatedwai_d0ff46e0": "Seu lugar é #%{spot} (Tempo estimado: %{estimatedWaitTime})"
}
}
}
2 changes: 1 addition & 1 deletion src/lib/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const Connection = {
if (timer) {
return;
}
timer = setTimeout(async() => {
timer = setTimeout(async () => {
try {
clearTimeout(timer);
timer = false;
Expand Down
6 changes: 5 additions & 1 deletion src/lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ export const loadConfig = async () => {
});
};

export const getToken = () => {
const { token } = store.state;
return token;
};

export const processUnread = async () => {
const { minimized, visible, messages } = store.state;
if (minimized || !visible) {
Expand All @@ -61,4 +66,3 @@ export const processUnread = async () => {
await store.setState({ unread: unreadMessages.length });
}
};

4 changes: 2 additions & 2 deletions src/lib/userPresence.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const awayTime = 300000;
let self;
let oldStatus;

const userPrensence = {
const userPresence = {

init() {
if (initiated) {
Expand Down Expand Up @@ -80,4 +80,4 @@ const userPrensence = {
},
};

export default userPrensence;
export default userPresence;
244 changes: 244 additions & 0 deletions src/lib/userSession.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
/* eslint-disable no-lonely-if */
/* eslint-disable no-alert */
import { getToken } from './main';
import { Livechat } from '../api';
import store from '../store';

const docActivityEvents = ['mousemove', 'mousedown', 'touchend', 'keydown'];
const token = getToken();
let timer;
let initiated = false;
const awayTime = 300000;
let self;
let oldStatus;

export const userSessionPresence = {

init() {
if (initiated) {
return;
}

initiated = true;
self = this;
store.on('change', this.handleStoreChange);
},

reset() {
initiated = false;
this.stopEvents();
store.off('change', this.handleStoreChange);
},

stopTimer() {
timer && clearTimeout(timer);
},

startTimer() {
this.stopTimer();
timer = setTimeout(this.setAway, awayTime);
},

handleStoreChange(state) {
if (!initiated) {
return;
}

const { token } = state;
token ? self.startEvents() : self.stopEvents();
},

startEvents() {
docActivityEvents.forEach((event) => {
document.addEventListener(event, this.setOnline);
});

window.addEventListener('focus', this.setOnline);
},

stopEvents() {
docActivityEvents.forEach((event) => {
document.removeEventListener(event, this.setOnline);
});

window.removeEventListener('focus', this.setOnline);
this.stopTimer();
},

async setOnline() {
self.startTimer();
if (oldStatus === 'online') {
return;
}
oldStatus = 'online';

await Livechat.updateSessionStatus('online', token);
},

async setAway() {
self.stopTimer();
if (oldStatus === 'away') {
return;
}
oldStatus = 'away';
await Livechat.updateSessionStatus('away', token);
},
};

const deviceInfo = () => {
const module = {
options: [],
header: [navigator.platform, navigator.userAgent, navigator.appVersion, navigator.vendor, window.opera],
dataos: [
{ name: 'Windows Phone', value: 'Windows Phone', version: 'OS' },
{ name: 'Windows', value: 'Win', version: 'NT' },
{ name: 'iPhone', value: 'iPhone', version: 'OS' },
{ name: 'iPad', value: 'iPad', version: 'OS' },
{ name: 'Kindle', value: 'Silk', version: 'Silk' },
{ name: 'Android', value: 'Android', version: 'Android' },
{ name: 'PlayBook', value: 'PlayBook', version: 'OS' },
{ name: 'BlackBerry', value: 'BlackBerry', version: '/' },
{ name: 'Macintosh', value: 'Mac', version: 'OS X' },
{ name: 'Linux', value: 'Linux', version: 'rv' },
{ name: 'Palm', value: 'Palm', version: 'PalmOS' },
],
databrowser: [
{ name: 'Chrome', value: 'Chrome', version: 'Chrome' },
{ name: 'Firefox', value: 'Firefox', version: 'Firefox' },
{ name: 'Safari', value: 'Safari', version: 'Version' },
{ name: 'Internet Explorer', value: 'MSIE', version: 'MSIE' },
{ name: 'Opera', value: 'Opera', version: 'Opera' },
{ name: 'BlackBerry', value: 'CLDC', version: 'CLDC' },
{ name: 'Mozilla', value: 'Mozilla', version: 'Mozilla' },
],
init() {
const agent = this.header.join(' ');
const os = this.matchItem(agent, this.dataos);
const browser = this.matchItem(agent, this.databrowser);

return { os, browser };
},
matchItem(string, data) {
let i = 0;
let j = 0;
let regex;
let regexv;
let match;
let matches;
let version;

for (i = 0; i < data.length; i += 1) {
regex = new RegExp(data[i].value, 'i');
match = regex.test(string);
if (match) {
regexv = new RegExp(`${ data[i].version }[- /:;]([\\d._]+)`, 'i');
matches = string.match(regexv);
version = '';
if (matches) { if (matches[1]) { matches = matches[1]; } }
if (matches) {
matches = matches.split(/[._]+/);
for (j = 0; j < matches.length; j += 1) {
if (j === 0) {
version += `${ matches[j] }.`;
} else {
version += matches[j];
}
}
} else {
version = '0';
}
return {
name: data[i].name,
version: parseFloat(version),
};
}
}
return { name: 'unknown', version: 0 };
},
};

const info = module.init();
return {
os: info.os.name,
osVersion: info.os.version,
browserName: info.browser.name,
browserVersion: info.browser.version,
};
};

export const userSessionWithoutLocation = {
token,
deviceInfo: deviceInfo(),
};


/**
* This is used to convert location to a default type we want to send to server
* @param {Object} location
* @returns {Object}
*/
const convertLocationToSend = (location) => (
{
countryName: location.country || location.country_name,
countryCode: location.country_code,
city: location.city || location.state,
latitude: location.latitude,
longitude: location.longitude,
completLocation: `${ location.country }, ${ location.state }, ${ location.city }`,
});

/**
* This is used to get location details for user
* @param {Number} latitude
* @param {Number} longitude
* @returns {Object}
*/
const sessionInfo = async (latitude, longitude) => {
const { address } = await fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${ latitude }&lon=${ longitude }`, {
mode: 'cors',
headers: {
'Access-Control-Allow-Origin': '*',
},
}).then((res) => res.json());

const location = convertLocationToSend(address);
location.latitude = latitude;
location.longitude = longitude;

return {
location,
token,
deviceInfo: deviceInfo(),
};
};

/**
* This function works in following way
* 1. Asks for user location access
* 2. If not granted, sets locationAccess in store as false, just send the session information
* 3. If granted, sets location of user info to DB
*/
export const sessionUpdate = async () => {
if (navigator.geolocation) {
store.setState({
locationAccess: true,
});
navigator.geolocation.getCurrentPosition(async (position) => {
const userSession = await sessionInfo(position.coords.latitude, position.coords.longitude);
await Livechat.sendSessionData(userSession);
userSessionPresence.init();
}, async (err) => {
// This means user has denied location access
// We need then to confirm location before starting the chat
// Save state of location access inside store.
if (err) {
store.setState({
locationAccess: false,
});
userSessionPresence.init();
// Send user data without location
await Livechat.sendSessionData(userSessionWithoutLocation);
}
});
}
};
6 changes: 5 additions & 1 deletion src/routes/Chat/container.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,12 @@ export class ChatContainer extends Component {
}

await this.grantUser();
const { _id: rid } = await this.getRoom();
const { _id: rid, msgs } = await this.getRoom();
const { alerts, dispatch, token, user } = this.props;
// This is a hack to fix room state in session when new chat is started
if (msgs === 1) {
await Livechat.updateSessionStatus('online', token);
}
try {
this.stopTypingDebounced.stop();
await Promise.all([
Expand Down
1 change: 1 addition & 0 deletions src/routes/Register/container.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export class RegisterContainer extends Component {
await dispatch({ loading: true, department });
try {
await Livechat.grantVisitor({ visitor: { ...fields, token } });
await Livechat.updateVisitorSessionOnRegister({ visitor: { ...fields, token } });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just one method to update the livechat session?
You just need one method, you can pass specific data as parameters.

parentCall('callback', ['pre-chat-form-submit', fields]);
await loadConfig();
} finally {
Expand Down
15 changes: 14 additions & 1 deletion widget-demo.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<title>This is home page</title>
<style>
body {
margin: 0;
Expand All @@ -23,6 +24,18 @@
</head>

<body>
<h1>Navigation</h1>
<ul>
<li>
<a href="page1.html">Page 1</a>
</li>
<li>
<a href="page2.html">Page 2</a>
</li>
<li>
<a href="page3.html">Page 3</a>
</li>
</ul>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Eos, inventore earum! Alias dolorum aliquam et quis iure, illo ipsum a voluptas repudiandae necessitatibus vitae accusamus at nobis asperiores sint eius?
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Odit voluptate corporis atque quam a dolorum, libero quos quisquam delectus blanditiis. Magnam culpa aut, voluptate dolore odio neque tempora rem commodi.
Expand Down
Loading