Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/jsxc.nextcloud.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/ts
diff options
context:
space:
mode:
authorsualko <klaus@jsxc.org>2018-09-05 12:05:56 +0300
committersualko <klaus@jsxc.org>2018-10-17 14:03:54 +0300
commit8d04c91ee21ba45c51b5e7391f5182f214e196ee (patch)
treec695650189fa936295861544dfe0d79991b4ffd0 /ts
parente5214aa68c39c0ebeb53be2a3b1c8cdb2797b7eb (diff)
feat: migrate to jsxc v4.0.0-alpha
- use webpack to build packages - use typescript
Diffstat (limited to 'ts')
-rw-r--r--ts/Bootstrap.ts33
-rw-r--r--ts/CONST.ts7
-rw-r--r--ts/ChatIconInjector.ts11
-rw-r--r--ts/ChatSubmitButtonInjector.ts41
-rw-r--r--ts/ContactMenuObserver.todo90
-rw-r--r--ts/DefaultAvatar.ts84
-rw-r--r--ts/FormWatcher.ts55
-rw-r--r--ts/InternalBackend.todo64
-rw-r--r--ts/SettingsLoader.ts78
-rw-r--r--ts/SettingsStore.ts13
-rw-r--r--ts/Storage.ts47
-rw-r--r--ts/UsersLoader.ts13
-rw-r--r--ts/index.ts33
-rw-r--r--ts/utils/Viewport.ts18
14 files changed, 587 insertions, 0 deletions
diff --git a/ts/Bootstrap.ts b/ts/Bootstrap.ts
new file mode 100644
index 0000000..c8850bf
--- /dev/null
+++ b/ts/Bootstrap.ts
@@ -0,0 +1,33 @@
+import { DEPENDENCIES } from './CONST'
+
+export default class Bootstrap {
+ public static start() {
+ Bootstrap.checkDependencies();
+ Bootstrap.checkFrame();
+ Bootstrap.checkSpecialPage();
+ }
+
+ private static checkDependencies() {
+ for (let dependency of DEPENDENCIES) {
+ if (typeof (<any>window)[dependency] === 'undefined') {
+ throw `Dependency "${dependency}" is missing.`;
+ }
+ }
+ }
+
+ private static checkFrame() {
+ if (window.parent && window !== window.parent) {
+ throw `Abort, because we are running inside a frame.`;
+ }
+ }
+
+ private static checkSpecialPage() {
+ if (/^(\/index.php)?\/s\//.test(location.pathname)) {
+ throw `Abort, because we dont want to start chat on public shares.`;
+ }
+
+ if (OC.generateUrl('login/flow') === window.location.pathname) {
+ throw `Abort, because chat is not needed on flow login.`;
+ }
+ }
+}
diff --git a/ts/CONST.ts b/ts/CONST.ts
new file mode 100644
index 0000000..9b670cf
--- /dev/null
+++ b/ts/CONST.ts
@@ -0,0 +1,7 @@
+export const SERVER_TYPE = {
+ INTERNAL: 0,
+ EXTERNAL: 1,
+ MANAGED: 2
+};
+
+export const DEPENDENCIES = ['jsxc', 'oc_config', 'oc_appswebroots', 'OC'];
diff --git a/ts/ChatIconInjector.ts b/ts/ChatIconInjector.ts
new file mode 100644
index 0000000..7e7ce9d
--- /dev/null
+++ b/ts/ChatIconInjector.ts
@@ -0,0 +1,11 @@
+
+export default function injectChatIcon() {
+ var div = $('<div/>');
+
+ div.addClass('jsxc_chatIcon');
+ div.click(function() {
+ jsxc.toggleRoster();
+ });
+
+ $('#header form.searchbox').after(div);
+}
diff --git a/ts/ChatSubmitButtonInjector.ts b/ts/ChatSubmitButtonInjector.ts
new file mode 100644
index 0000000..de04fd6
--- /dev/null
+++ b/ts/ChatSubmitButtonInjector.ts
@@ -0,0 +1,41 @@
+import Storage from './Storage'
+
+function addChatSubmitButton(formElement: JQuery<any>) {
+ let storage = Storage.get();
+ let defaultEnable = OJSXC_CONFIG.defaultLoginFormEnable;
+ let submitWrapperElement = $('<div>');
+ submitWrapperElement.attr('id', 'jsxc-submit-wrapper');
+
+ let submitElement = $('<input>');
+ submitElement.attr({
+ type: 'button',
+ id: 'jsxc-submit',
+ });
+ submitElement.addClass('login primary');
+ if (defaultEnable) {
+ submitElement.val('Log_in_without_chat'); //@TODO translate
+ submitElement.click(function() {
+ // submit form without login
+ });
+ } else {
+ submitElement.val('Log_in_with_chat');
+ submitElement.click(function() {
+ let forceLoginFormEnable = true;
+ formElement.submit();
+ });
+ }
+
+ submitWrapperElement.append(submitElement);
+ $('.login-additional').prepend(submitWrapperElement);
+
+ $('#lost-password').mouseup(function(ev) {
+ ev.preventDefault();
+
+ submitWrapperElement.slideUp().fadeOut();
+ });
+ $('#lost-password-back').mouseup(function(ev) {
+ ev.preventDefault();
+
+ submitWrapperElement.slideDown().fadeIn();
+ });
+}
diff --git a/ts/ContactMenuObserver.todo b/ts/ContactMenuObserver.todo
new file mode 100644
index 0000000..112af21
--- /dev/null
+++ b/ts/ContactMenuObserver.todo
@@ -0,0 +1,90 @@
+function observeContactsMenu() {
+ var target = document.getElementById('contactsmenu');
+
+ var observer = new MutationObserver(function (mutations) {
+ mutations.forEach(function (mutation) {
+ if (mutation.target.id !== 'contactsmenu-contacts') {
+ return;
+ }
+
+ $(mutation.target).find('[href^="xmpp:"]').addClass('jsxc_statusIndicator');
+
+ $(mutation.target).find('.contact').each(function () {
+ updateContactItem($(this));
+ });
+
+ jsxc.gui.detectUriScheme(mutation.target);
+ });
+ });
+
+ var config = {
+ attributes: true,
+ childList: true,
+ characterData: true,
+ subtree: true
+ };
+
+ observer.observe(target, config);
+}
+
+function updateContactItem(contactElement) {
+ var xmppAddresses = contactElement.find('[href^="xmpp:"]').map(function () {
+ return $(this).attr('href').replace(/^xmpp:/, '');
+ });
+
+ if (xmppAddresses.length === 0) {
+ return;
+ }
+
+ var lastMessages = [];
+ var highestPresent = jsxc.CONST.STATUS.indexOf('offline');
+ var highestPresentBid = xmppAddresses.get(0);
+
+ xmppAddresses.each(function (index, bid) {
+ var lastMsg = jsxc.getLastMsg(bid);
+
+ if (lastMsg) {
+ lastMessages.push(lastMsg);
+ }
+
+ var data = jsxc.storage.getUserItem('buddy', bid) || {};
+
+ if (data.status > highestPresent) {
+ highestPresent = data.status;
+ highestPresentBid = bid;
+ }
+ });
+
+ var latestMsg = {
+ date: 0
+ };
+ $(lastMessages).each(function (index, msg) {
+ if (msg.date > latestMsg.date) {
+ latestMsg = msg;
+ }
+ });
+
+ if (latestMsg.date > 0) {
+ // replace emoticons from XEP-0038 and pidgin with shortnames
+ $.each(jsxc.gui.emotions, function (i, val) {
+ latestMsg.text = latestMsg.text.replace(val[2], ':' + val[1] + ':');
+ });
+
+ // translate shortnames to images
+ latestMsg.text = jsxc.gui.shortnameToImage(latestMsg.text);
+
+ contactElement.find('.last-message').html(latestMsg.text);
+ }
+
+ if (highestPresent > 0) {
+ var status = jsxc.CONST.STATUS[highestPresent];
+
+ contactElement.removeClass('jsxc_' + jsxc.CONST.STATUS.join(' jsxc_')).addClass('jsxc_' + status);
+ }
+
+ if (highestPresentBid) {
+ contactElement.find('.avatar').click(function () {
+ jsxc.gui.queryActions.message(highestPresentBid);
+ });
+ }
+} \ No newline at end of file
diff --git a/ts/DefaultAvatar.ts b/ts/DefaultAvatar.ts
new file mode 100644
index 0000000..1967caf
--- /dev/null
+++ b/ts/DefaultAvatar.ts
@@ -0,0 +1,84 @@
+import Storage from "./Storage";
+import { IJID } from "jsxc/src/JID.interface";
+
+enum Presence {
+ online,
+ chat,
+ away,
+ xa,
+ dnd,
+ offline
+}
+
+interface Avatar {
+ username: string,
+ type: 'url' | 'placeholder',
+ displayname?: string,
+ url?: string;
+}
+
+export default function defaultAvatar(elements, jid: IJID, name: string, presence) {
+ let storage = Storage.get();
+
+ let adminSettings = storage.getItem('adminSettings') || {};
+
+ $(elements).each(function() {
+ let element = $(this);
+
+ if (element.length === 0) {
+ return;
+ }
+
+ let size = <number>element.width();
+ let username = jid.node;
+ let key = username + '@' + size;
+ let cache = storage.getItem('avatar:' + key);
+ let isExternalUser = jid.domain !== adminSettings.xmppDomain;
+
+ if (cache) {
+ displayAvatar(element, cache);
+ } else if (isExternalUser || presence === Presence.offline) {
+ setPlaceholder(element, username);
+ } else {
+ requestAvatar(element, username, size);
+ }
+ });
+}
+
+function requestAvatar(element: JQuery<any>, username: string, size: number) {
+ let url = getAvatarUrl(username, size);
+
+ $.get(url, function(result) {
+ let avatar: Avatar = {
+ username: username,
+ type: typeof result === 'string' ? 'url' : 'placeholder',
+ displayname: result.data && result.data.displayname ? result.data.displayname : undefined,
+ url: typeof result === 'string' ? result : undefined,
+ };
+
+ displayAvatar(element, avatar);
+
+ Storage.get().setItem('avatar:' + username + '@' + size, avatar);
+ });
+}
+
+function displayAvatar(element, avatar: Avatar) {
+ if (avatar.type === 'url') {
+ element.css('backgroundImage', 'url(' + avatar.url + ')');
+ element.text('');
+ } else {
+ setPlaceholder(element, avatar.username, avatar.displayname);
+ }
+}
+
+function getAvatarUrl(username: string, size: number) {
+ return OC.generateUrl('/avatar/' + encodeURIComponent(username) + '/' + size + '?requesttoken={requesttoken}', {
+ user: username,
+ size: size,
+ requesttoken: oc_requesttoken
+ })
+}
+
+function setPlaceholder(element, username: string, displayname?: string) {
+ (<any>element).imageplaceholder(username, displayname);
+}
diff --git a/ts/FormWatcher.ts b/ts/FormWatcher.ts
new file mode 100644
index 0000000..bab8b77
--- /dev/null
+++ b/ts/FormWatcher.ts
@@ -0,0 +1,55 @@
+
+export default class FormWatcher {
+ public static callback(username: string, password: string) {
+ return new Promise(resolve => {
+ $.ajax({
+ type: 'POST',
+ url: OC.generateUrl('apps/ojsxc/settings'),
+ data: {
+ username: username,
+ password: password
+ },
+ success: response => FormWatcher.success(response, resolve),
+ error: xhr => FormWatcher.error(xhr, resolve),
+ });
+ });
+ }
+
+ private static success(response, resolve) {
+ if (response.result !== 'success') {
+ resolve(false);
+
+ return;
+ }
+
+ let xmpp = response.data.xmpp || {};
+
+ if (!xmpp.url) {
+ resolve(false);
+
+ return;
+ }
+
+ resolve({
+ xmpp: {
+ url: xmpp.url,
+ domain: xmpp.domain,
+ }
+ });
+ }
+
+ private static error(xhr, resolve) {
+ if (xhr.responseJSON && xhr.responseJSON.message) {
+ throw 'Error message: ' + xhr.responseJSON.message;
+ }
+
+ if (xhr.status === 412) {
+ console.log('Refresh page to get a new CSRF token');
+
+ window.location.href = window.location.href;
+ return;
+ }
+
+ resolve(false);
+ }
+}
diff --git a/ts/InternalBackend.todo b/ts/InternalBackend.todo
new file mode 100644
index 0000000..903d402
--- /dev/null
+++ b/ts/InternalBackend.todo
@@ -0,0 +1,64 @@
+$(document).on('click', '#jsxc_roster p', function () {
+ if (jsxc.storage.getItem('serverType') === serverTypes.INTERNAL) {
+ startInternalBackend();
+ }
+});
+
+function startInternalBackend() {
+ var currentUser = OC.currentUser;
+
+ if (!currentUser) {
+ return;
+ }
+
+ jsxc.bid = currentUser.toLowerCase() + '@' + window.location.host;
+
+ jsxc.options.set('xmpp', {
+ url: OC.generateUrl('apps/ojsxc/http-bind')
+ });
+
+ $(document).one('attached.jsxc', function () {
+ if (jsxc.options.get('loginForm').startMinimized !== true) {
+ jsxc.gui.roster.toggle(jsxc.CONST.SHOWN);
+ }
+ });
+
+ jsxc.start(jsxc.bid + '/internal', 'internal', '123456');
+}
+
+
+if (jsxc.storage.getItem('serverType') === serverTypes.INTERNAL) {
+ jsxc.gui.showLoginBox = function() {};
+ }
+
+ $(document).on('stateChange.jsxc', function _handler(event, state) {
+ if (state === jsxc.CONST.STATE.SUSPEND) {
+ /**
+ * The first time we go into suspend mode we check if we are using the internal backend.
+ * If this is the case and the user explicitly press the "login_without_chat" button when logging
+ * into Nextcloud we know we are using another authentication mechanism (like SAML/SSO) and thus have
+ * to manually start the connection.
+ */
+ var chatDisabledByUser = jsxc.storage.getUserItem('forcedLogout') || jsxc.storage.getItem('login_without_chat');
+ $(document).off('stateChange.jsxc', _handler);
+ if (jsxc.storage.getItem('serverType') === null) {
+ $.ajax({
+ url: OC.generateUrl('apps/ojsxc/settings/servertype'),
+ success: function(data) {
+ jsxc.storage.setItem('serverType', serverTypes[data.serverType.toUpperCase()]);
+
+ if (data.serverType === 'internal' && !chatDisabledByUser) {
+ jsxc.gui.showLoginBox = function() {};
+ startInternalBackend();
+ }
+ }
+ });
+ } else if (jsxc.storage.getItem('serverType') === serverTypes.INTERNAL && !chatDisabledByUser) {
+ jsxc.gui.showLoginBox = function() {};
+ startInternalBackend();
+ }
+ } else if (state === jsxc.CONST.STATE.READY) {
+ // if JSXC is ready this means we successfully connected and thus don't have to listen to the suspend state
+ $(document).off('stateChange.jsxc', _handler);
+ }
+ }); \ No newline at end of file
diff --git a/ts/SettingsLoader.ts b/ts/SettingsLoader.ts
new file mode 100644
index 0000000..2f4407c
--- /dev/null
+++ b/ts/SettingsLoader.ts
@@ -0,0 +1,78 @@
+import { SERVER_TYPE } from "./CONST";
+import Storage from "./Storage";
+
+export function loadSettings(username, password) {
+ return new Promise((resolve, reject) => {
+ $.ajax({
+ type: 'POST',
+ url: OC.generateUrl('apps/ojsxc/settings'),
+ data: {
+ username: username,
+ password: password
+ },
+ success: (d) => resolve(d),
+ error: xhr => reject(xhr),
+ });
+ })
+ .then(handleResponse)
+ .catch(handleError);
+}
+
+function handleResponse(response) {
+ if (response.result !== 'success' || !response.data) {
+ throw 'Received unsuccessful response.';
+ }
+
+ let data = response.data;
+ let serverType = SERVER_TYPE[data.serverType.toUpperCase()];
+ let xmpp = data.xmpp || {};
+
+ Storage.get().setItem('serverType', serverType);
+
+ if (serverType !== SERVER_TYPE.INTERNAL && xmpp.url) {
+
+ // if (forceLoginFormEnable) {
+ // response.data.loginForm.enable = true;
+ // }
+
+ return {
+ xmpp: {
+ url: xmpp.url,
+ domain: xmpp.domain,
+ }
+ };
+ } else if (serverType === SERVER_TYPE.INTERNAL) {
+ // var node = username || OC.currentUser;
+ // jsxc.bid = node.toLowerCase() + '@' + window.location.host;
+
+ // jsxc.options.set('adminSettings', response.data.adminSettings);
+
+ // if (response.data.loginForm) {
+ // jsxc.options.set('loginForm', {
+ // startMinimized: response.data.loginForm.startMinimized
+ // });
+ // }
+ }
+
+ Storage.get().removeItem('serverType');
+
+ return false;
+}
+
+function handleError(xhr) {
+ console.warn('XHR error on getSettings.php');
+
+ if (xhr.responseJSON && xhr.responseJSON.message) {
+ console.log('Error message: ' + xhr.responseJSON.message);
+ }
+
+ if (xhr.status === 412) {
+ console.log('Refresh page to get a new CSRF token');
+
+ window.location.href = window.location.href;
+
+ return new Promise(() => { });
+ }
+
+ return false;
+}
diff --git a/ts/SettingsStore.ts b/ts/SettingsStore.ts
new file mode 100644
index 0000000..e493f49
--- /dev/null
+++ b/ts/SettingsStore.ts
@@ -0,0 +1,13 @@
+function saveSettinsPermanent(data, cb) {
+ $.ajax({
+ type: 'POST',
+ url: OC.generateUrl('apps/ojsxc/settings/user'),
+ data: data,
+ success: function(data) {
+ cb(data && data.status === 'success');
+ },
+ error: function() {
+ cb(false);
+ }
+ });
+}
diff --git a/ts/Storage.ts b/ts/Storage.ts
new file mode 100644
index 0000000..9d3d8a9
--- /dev/null
+++ b/ts/Storage.ts
@@ -0,0 +1,47 @@
+
+const PREFIX = 'ojsxc:';
+
+const BACKEND = localStorage;
+
+export default class Storage {
+
+ private static instance;
+
+ public static get(): Storage {
+ if (!Storage.instance) {
+ Storage.instance = new Storage();
+ }
+
+ return Storage.instance;
+ }
+
+ private constructor() {
+
+ }
+
+ public setItem(key: string, value: any): void {
+ try {
+ value = JSON.stringify(value);
+ } catch (err) {
+ console.warn('Error while stringifing', err);
+
+ return;
+ }
+
+ BACKEND.setItem(PREFIX + key, value);
+ }
+
+ public getItem(key: string): any {
+ let value = BACKEND.getItem(PREFIX + key);
+
+ if (typeof value === 'string') {
+ value = JSON.parse(value);
+ }
+
+ return value;
+ }
+
+ public removeItem(key: string): void {
+ BACKEND.removeItem(PREFIX + key);
+ }
+}
diff --git a/ts/UsersLoader.ts b/ts/UsersLoader.ts
new file mode 100644
index 0000000..fb48f83
--- /dev/null
+++ b/ts/UsersLoader.ts
@@ -0,0 +1,13 @@
+function getUsers(search, cb) {
+ $.ajax({
+ type: 'GET',
+ url: OC.generateUrl('apps/ojsxc/settings/users'),
+ data: {
+ search: search
+ },
+ success: cb,
+ error: function() {
+ console.warn('XHR error on getUsers.php');
+ }
+ });
+}
diff --git a/ts/index.ts b/ts/index.ts
new file mode 100644
index 0000000..4c0bf0a
--- /dev/null
+++ b/ts/index.ts
@@ -0,0 +1,33 @@
+// import jsxc from 'jsxc/src';
+import FormWatcher from './FormWatcher';
+import Bootstrap from './Bootstrap';
+import { loadSettings } from './SettingsLoader';
+import injectChatIcon from './ChatIconInjector';
+
+(function() {
+ try {
+ Bootstrap.start();
+ } catch (err) {
+ console.warn('Abort JSXC', err);
+
+ return;
+ }
+
+ let numberOfCachedAccounts = jsxc.init({
+ loadSettings: loadSettings,
+ });
+
+ if (numberOfCachedAccounts === 0 && oc_current_user) {
+ jsxc.start();
+ }
+
+ injectChatIcon();
+
+ let formElement = $('#body-login form');
+ let usernameElement = $('#user');
+ let passwordElement = $('#password');
+
+ if (formElement.length > 0) {
+ jsxc.watchForm(formElement, usernameElement, passwordElement, FormWatcher.callback);
+ }
+})();
diff --git a/ts/utils/Viewport.ts b/ts/utils/Viewport.ts
new file mode 100644
index 0000000..0c2f37a
--- /dev/null
+++ b/ts/utils/Viewport.ts
@@ -0,0 +1,18 @@
+import Storage from '../Storage'
+
+export default class Viewport {
+ public static getSize(): { width: number, height: number } {
+ var w = <number>$(window).width() - <number>$('#jsxc_windowListSB').width();
+ var h = <number>$(window).height() - <number>$('#header').height() - 10;
+
+ //@TODO
+ if (Storage.get().getItem('roster') === 'shown') {
+ w -= <number>$('#jsxc-roster').outerWidth(true);
+ }
+
+ return {
+ width: w,
+ height: h
+ };
+ }
+}