Cycle.js - Драйвер - PhoenixJS (веб-сокеты) - PullRequest
0 голосов
/ 14 декабря 2018

В настоящее время у нас есть приложение VueJS, и я планирую перенести его в Cycle.js (первый крупный проект).

Я понимаю, что в Cycle.JS у нас есть SI и SO для драйверов (с использованием adapt());Естественно, реализация WebSocket подходит для этого, поскольку имеет эффекты как чтения, так и записи.

Мы используем Phoenix (Elixir) в качестве нашего бэкенда, используя каналы для мягкой коммуникации в реальном времени.Наша клиентская библиотека WS - это Phoenix здесь https://www.npmjs.com/package/phoenix.

Пример на Cycle.js.org идеально подходит, если вы знаете, как подключиться.

В нашем случаемы аутентифицируемся, используя конечную точку REST, которая возвращает токен (JWT), который используется для инициализации WebSocket (параметр токена).Этот токен нельзя просто передать в драйвер, так как драйвер инициализируется при запуске приложения Cycle.js.

Пример (не фактический код) того, что мы имеем сейчас (в нашем приложении VueJS):

// Code ommited for brevity 
socketHandler = new vueInstance.$phoenix.Socket(FQDN, {
    _token: token
});
socketHandler.onOpen(() => VueBus.$emit('SOCKET_OPEN'));

//...... Vue component (example)
VueBus.$on('SOCKET_OPEN', function () {
    let chan = VueStore.socketHandler.channel('PRIV_CHANNEL', {
        _token: token
    });

    chan.join()
        .receive('ok', () => {
            //... code
        })
})

Выше приведен пример, у нас есть хранилище Vuex для глобального состояния (сокет и т. Д.), Централизованная шина сообщений (приложение Vue) для связи между компонентами и настройками канала, которые поступают из экземпляра Phoenix Socket..

Наша настройка канала основана на аутентифицированном соединении Socket, которому требуется сама аутентификация для подключения к этому конкретному каналу.

Вопрос в том, возможно ли это даже с Cycle.js?

  1. Инициализация соединения WebSocket с параметрами токена из вызова REST (ответ токена JWT) - мы реализовали это частично
  2. Создание каналов на основе этого сокета и токена ( канал)потоков из драйвера? )
  3. Доступ к нескольким канальным потокам ( Я предполагаю, что это может сработатьe sources.HTTP.select (CATEGORY) )

У нас есть зависимость 1: N, которая, я не уверен, возможна с драйверами.

Заранее спасибо,

Обновление @ 17/12/2018

По сути, я пытаюсь подражать следующему (из Cycle.js.org):

Драйвер принимает приемник для выполнения эффектов записи (отправки сообщений по определенным каналам), но также может возвращать источник;это означает, что есть два потока, которые являются асинхронными?Это означает, что создание сокета во время выполнения может привести к тому, что один поток получит доступ к «сокету» до его создания;пожалуйста, смотрите комментарии во фрагменте ниже.

import {adapt} from '@cycle/run/lib/adapt';

function makeSockDriver(peerId) {
  // This socket may be created at an unknown period
  //let socket = new Sock(peerId);
  let socket = undefined;

  // Sending is perfect
  function sockDriver(sink$) {
    sink$.addListener({
      next: listener => {

        sink$.addListener({
                next: ({ channel, data }) => {
                    if(channel === 'OPEN_SOCKET' && socket === null) {
                        token = data;

                        // Initialising the socket
                        socket = new phoenix.Socket(FQDN, { token });
                        socketHandler.onOpen(() => listener.next({
                            channel: 'SOCKET_OPEN'
                        }));
                    } else {
                        if(channels[channel] === undefined) {
                            channels[channel] = new Channel(channel, { token });
                        }
                        channels[channel].join()
                            .receive('ok', () => {
                                sendData(data);
                            });
                    }
                }
            });
      },
      error: () => {},
      complete: () => {},
    });

    const source$ = xs.create({
      start: listener => {
        sock.onReceive(function (msg) {
            // There is no guarantee that "socket" is defined here, as this may fire before the socket is actually created 
            socket.on('some_event'); // undefined

            // This works however because a call has been placed back onto the browser stack which probably gives the other blocking thread chance to write to the local stack variable "socket". But this is far from ideal
            setTimeout(() => socket.on('some_event'));
        });
      },
      stop: () => {},
    });

    return adapt(source$);
  }

  return sockDriver;
}

Ян ван Брюгге, предоставленный вами солютон идеален (спасибо), за исключением того, что у меня возникли проблемы с ответной частью.Пожалуйста, смотрите приведенный выше пример.

Например, я пытаюсь добиться чего-то вроде этого:

// login component
return {
    DOM: ...
    WS: xs.of({
        channel: "OPEN_CHANNEL",
        data: {
            _token: 'Bearer 123'
        }
    })
}

//////////////////////////////////////
// Some authenticated component

// Intent
const intent$ = sources.WS.select(CHANNEL_NAME).startWith(null)

// Model
const model$ = intent$.map(resp => {
    if (resp.some_response !== undefined) {
        return {...}; // some model
    }
    return resp;
})

return {
    DOM: model$.map(resp => {
        // Use response from websocket to create UI of some sort
    })
}

1 Ответ

0 голосов
/ 14 декабря 2018

Прежде всего, да, это возможно с драйвером, и мое предложение приведет к тому, что драйвер будет выглядеть как драйвер HTTP.

Прежде всего, иметь некоторый грубый псевдокод, где я могуобъясните все, я мог бы неправильно понять части вашего вопроса, так что это может быть неправильно.

interface WebsocketMessage {
    channel: string;
    data: any;
}

function makeWebSocketDriver() {
    let socket = null;
    let token = null;
    let channels = {}
    return function websocketDriver(sink$: Stream<WebsocketMessage> {
        return xs.create({
            start: listener => {
                sink$.addListener({
                    next: ({ channel, data }) => {
                        if(channel === 'OPEN_SOCKET' && socket === null) {
                            token = data;
                            socket = new phoenix.Socket(FQDN, { token });
                            socketHandler.onOpen(() => listener.next({
                                channel: 'SOCKET_OPEN'
                            }));
                        } else {
                            if(channels[channel] === undefined) {
                                channels[channel] = new Channel(channel, { token });
                            }
                            channels[channel].join()
                                .receive('ok', () => {
                                    sendData(data);
                                });
                        }
                    }
                });
            }
        });
    };
}

Это будет грубая структура такого драйвера.Вы видите, что он ждет сообщения с токеном, а затем открывает сокет.Он также отслеживает открытые каналы и отправляет / получает те, которые основаны на категории сообщения.Этот метод просто требует, чтобы все каналы имели уникальные имена, я не уверен, как ваш протокол канала работает в этом отношении или что вы хотите, в частности.

Я надеюсь, что этого достаточно, чтобы вы начали, если вы поясните APIканала отправки / получения и сокета, я мог бы помочь больше.Вы также всегда можете задать вопросы в нашем канале gitter

...