brillout / wildcard-api

Functions as API.
MIT License
368 stars 14 forks source link

Real-time/bi-directional communication (Wildcard callbacks) #61

Open brillout opened 4 years ago

brillout commented 4 years ago

The idea is to allow the client to pass a callback to the server:

// Browser-side

// We define a callback which will be called by the server!
function callback(event) {
  console.log('Message from the server: '+event.message);
}

// We pass `callback` to the server.
server.subscribeEvents(callback);
// Server-side

server.subscribeEvents = async function(callback) {
  callback({message: 'Hello!'});
  await sleep({seconds: 3});
  callback({message: 'This is another message from the server.'});
};

This enables all kinds of bi-directional communications, for example real-time chat, real-time gaming, live subscriptions, etc.

Proposed name: Wildcard callbacks.

The lifecycle can be managed thanks to two _onClose functions provided by Wildcard, as described in the following example.

Example - Live chat

// Browser-side

import { server } from '@wildcard-api/client';

main();

async function joinChat(roomId) {
  const { leave } = await server.joinChatRoom(
    roomId,
    // Our client-side defined `onNewMessage` is called by the server.
    onNewMessage,
  );

  print(`Joined chat room ${roomId}.`);

  // Leave chat room after 30 seconds
  setTimeout(
    () => {
      leave();
      print(`Left chat room ${roomId}.`);
    },
    30*1000
  );
}

server._onClose = function() {
  // Browser is offline, or server is down.
  // All callbacks are now closed.
  print('Lost connection to server');
}

async function main() {
  const roomId = 123;
  await joinChat(roomId);
};

async function onNewMessage(message) {
  print(message);
}

function print(text) {
   // (One should never do this; prone to JS injection.)
   document.body.innerHTML += `<div>${text}</div>`;
}
// Server-side

import { server } from '@wildcard-api/server';
const chatRooms = require('./path/to/chatRooms');

server.joinChatRoom = async function(roomId, onNewMessage) {
  const removeListener = chatRooms[roomId].addListener(message => {
    onNewMessage(message);
  });

  function leave() {
    removeListener();
  }

  this._onClose = function() {
    // The connection to the client is lost
    //  - The user closed his browser, or
    //  - There is a network connection problem
    leave();
  };

  // We return `leave` to the client so that
  // the frontend can leave the chat room.
  return { leave };
};