diff --git a/assets/js/api.js b/assets/js/api.js index 5f02e89..6893ee0 100644 --- a/assets/js/api.js +++ b/assets/js/api.js @@ -1,255 +1,258 @@ import {Socket, LongPoll, Presence} from "phoenix" import UserListWidget from "./widgets/userlist" import UserStatusWidget from "./widgets/userstatus" import ChatInputWidget from "./widgets/chatinput" import ChatRoomWidget from "./widgets/chatroom" class KolabChat { /** * Configuration parameters: * - token: User session token * - context: KolabChat instance identifier * - roomId: Chat room Id to join in * - userListElement: Id of HTML element where to put userslist widget * - userStatusElement: Id of HTML element where to put users status widget * - chatInputElement: Id of HTML element which is a text chat input * - chatRoomElement: Id of HTML element where to put text conversation */ constructor(config) { this.config = config || {} if (!this.config.context) this.config.context = location.hostname; } /** * Initialize WebSocket communication */ init(config) { if (config) this.config = KolabChat.extend(this.config, config) this.initWidgets() // TODO: for integration with external systems we'll use configurable full wss:// url this.socket = new Socket("/socket", { params: {token: this.config.token, context: this.config.context}, logger: ((kind, msg, data) => { console.log(`${kind}: ${msg}`, data) }), }) this.socket.onOpen(e => { // when connected start using 'system' channel // for users' presence this.initPresence() if (this.config.roomId) { this.initRoom(this.config.roomId, this.config.invitees) } }) this.socket.connect() } /** * Initializes configured UI widgets */ initWidgets() { let config if (this.config.userListElement && $('#' + this.config.userListElement).length) { config = { username: this.username, openChat: (e, user) => { this.openChat(e, user) } } this.userListWidget = new UserListWidget(this.config.userListElement, config) } if (this.config.userStatusElement && $('#' + this.config.userStatusElement).length) { config = { username: this.username, statusChange: status => { this.setStatus(status) } } this.userStatusWidget = new UserStatusWidget(this.config.userStatusElement, config) } if (this.config.chatRoomElement && $('#' + this.config.chatRoomElement).length) { this.chatRoomWidget = new ChatRoomWidget(this.config.chatRoomElement) } if (this.config.chatInputElement && $('#' + this.config.chatInputElement).length) { config = { submit: (e, msg) => { this.sendTxtMessage(e, msg) } } this.chatInputWidget = new ChatInputWidget(this.config.chatInputElement, config) } } /** * Initialize user presence * Create users list and status widgets */ initPresence() { this.system = this.socket.channel("system", {context: this.config.context}) this.system.on("info", info => { this.username = info.user this.userId = info.userId if (this.userListWidget) { this.userListWidget.setUser(this.username, this.userId) } this.initNotifications(info.userId) }) this.system.on("presence_state", state => { this.presences = Presence.syncState({}, state) this.renderPresences(this.presences) }) this.system.on("presence_diff", diff => { // ignore initial presence_diff result, handle presence_state first if (this.presences !== undefined) { this.presences = Presence.syncDiff(this.presences, diff) this.renderPresences(this.presences) } }) this.system.join() } /** * Initialize chat channel */ initRoom(roomId, invitees) { - this.chat = this.socket.channel("room:" + roomId) + let channelName = roomId.startsWith("room:") ? roomId + : "room:" + roomId + this.chat = this.socket.channel(channelName) if (this.chatRoomWidget) { this.chat.on("new:message", message => { this.chatRoomWidget.append(message.user, message.body) }) } let join = this.chat.join() if (invitees) { join.receive("ok", () => this.chat.push("ctl:invite", {users: invitees})) } } /** * Initialize the user's own notifications channel */ initNotifications(userId) { this.notifications = this.socket.channel("user:" + userId) this.notifications.on("notify:invite", message => { console.log("Invite from " + message.user + " to room " + message.room) + this.openExistingChat(message.room) }) this.notifications.join() .receive("ok", () => console.log("Starting to receive notifications")) .receive("error", () => console.log("FAILED to subscribe to notifications. Uh-oh!")) .receive("timeout", () => console.log("notification subscription timedout")) } /** * Send text message to the chat room */ sendTxtMessage(event, message) { this.chat.push("new:message", { body: message }) } /** * Handler for presence responses */ renderPresences(presences) { let userPresence if (this.userStatusWidget && (userPresence = presences[this.userId])) { userPresence = this.listBy(this.username, userPresence, this.config.context) this.userStatusWidget.render(userPresence) } if (this.userListWidget) { presences = Presence.list(presences, this.listBy) this.userListWidget.render(presences) } } listBy(user, {metas: metas}, context) { let statusWeights = { offline: 0, busy: 10, away: 20, online: 30 } // Find "best" availability status for the user // If set, narrow the result to the current session context let mostAvailableStatus = metas.reduce((best, meta) => { if (context && context != meta.context) return best return statusWeights[best] > statusWeights[meta.status] ? best : meta.status }, "offline") return { user: user, username: metas[0].username, status: mostAvailableStatus } } /** * User status change */ setStatus(status) { this.system.push('set-status', {status: status}); } /** * Open chat window (and create a new chat room) */ openChat(event, user) { let windowName = 'KolabChat' + new Date().getTime() let url = "/chat/?token=" + encodeURIComponent(this.config.token) + "&invite=" + encodeURIComponent(user) var extwin = window.open(url, windowName); } /** * Open a chat window to an existing chat */ - openExistingChat(event, user, roomId) + openExistingChat(roomId) { let windowName = 'KolabChat' + new Date().getTime() let url = "/chat/" + encodeURIComponent(roomId) + "/?token=" + encodeURIComponent(this.config.token) var extwin = window.open(url, windowName); } static extend(obj, src) { Object.keys(src).forEach(function(key) { obj[key] = src[key] }) return obj } } export default KolabChat