123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202 |
- /*
- * Sapphire
- *
- * Copyright (C) 2018 Florrie Haero
- * Copyright (C) 2018 Alyssa Rosenzweig
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
- *
- */
- /* The active socket itself */
- let backendWS = null;
- /* Delay connecting to the websocket to prevent race conditions */
- const WS_PORT = 7070;
- window.backendConnect = function(username, passwordHash) {
- const protocol = 'ws' + (window.location.protocol === 'https:' ? 's': '') + '://';
- const url = protocol + window.BASE_HOST + ':' + WS_PORT + '/ws';
- /* Whether we ever managed to connect */
- let once_connected = false;
- console.log(`Connecting to ${url}...`);
- backendWS = new WebSocket(url);
- backendWS.onopen = function() {
- console.log('open');
- once_connected = true;
- backendWS.send(JSON.stringify({username, passwordHash}));
- };
- backendWS.onclose = function() {
- console.log('closed');
- sapphireCallHandler('wsclosed', once_connected);
- };
- backendWS.onmessage = sapphireGotMessage;
- };
- /* Maintain set of buddy/chats IDs */
- const backendIDType = {};
- /* Handlers to be filled in by the frontend */
- window.backendHooks = {
- message: null,
- batchMessage: null,
- newBuddy: null,
- newChat: null,
- newAccount: null,
- changeAvatar: null,
- joined: null,
- typing: null,
- topic: null,
- wsclosed: null
- };
- /* Serialize and send off an event to the server */
- function sendEvent(data) {
- backendWS.send(JSON.stringify(data));
- }
- function sendEventWithTarget(data, id) {
- data[backendIDType[id]] = id;
- sendEvent(data);
- }
- /* Events exposed to the frontend */
- window.backendMessage = (target, content) => {
- sendEventWithTarget({
- op: 'message', content
- }, target);
- };
- window.backendTyping = (target, state) => {
- sendEventWithTarget({
- op: 'typing', state
- }, target);
- };
- window.backendJoinChat = (id) => {
- sendEvent({
- op: 'joinChat', id
- });
- };
- window.backendMarkAsRead = (id) => {
- sendEvent({
- op: 'markAsRead', id
- });
- };
- window.backendRequestBuddy = (account, id, alias, invite) => {
- sendEvent({
- op: 'requestBuddy', account, id, alias, invite
- });
- };
- window.backendChangeAvatar = (account, base64) => {
- sendEvent({
- op: 'changeAvatar', account, base64
- });
- };
- /* Internal handlers */
- function sapphireGotWorld(data) {
- /* A world packet contains the buddy list and the account list. Iterate
- * these separately and defer to the usual handler */
- for (const account of data.accounts) {
- window.backendHooks.newAccount(account);
- }
- /* Make sure accounts are enabled before replaying messages */
- /* Function to replay missed messages, in the right (reversed) order */
- const unack = function(obj) {
- if (obj.unacked) {
- const unacked = obj.unacked.reverse();
- for (const message of unacked) {
- window.backendHooks.batchMessage(message);
- }
- }
- window.backendHooks.flushBatched();
- };
- for (const buddy of data.buddies) {
- backendIDType[buddy.id] = 'buddy';
- window.backendHooks.newBuddy(buddy);
- unack(buddy);
- }
- for (const chat of data.chats) {
- backendIDType[chat.id] = 'chat';
- window.backendHooks.newChat(chat);
- unack(chat);
- }
- }
- const sapphireCallHandler = (op, ...args) => {
- /* Handlers for internal use */
- const internalHandlers = {
- world: sapphireGotWorld
- };
- /* Invoke a handler */
- const handler = window.backendHooks[op] || internalHandlers[op];
- if (handler) {
- handler(...args);
- } else {
- console.error('Missing handler for op: ' + op);
- }
- };
- const sapphireGotMessage = (event) => {
- /* On receiving a message, we need to decode it (from JSON) and then
- * process it accordingly */
- let obj;
- const str = event.data;
- try {
- obj = JSON.parse(str);
- } catch(e) {
- console.error('Parse error\n');
- console.error('> ' + str);
- console.error(e);
- return;
- }
- console.log(obj);
- /* Parsed, now invoke a handler to actually do something with it */
- sapphireCallHandler(obj.op, obj);
- };
|