Cycle.js-驱动程序-PhoenixJS(网络套接字)

时间:2018-12-14 14:24:57

标签: javascript vue.js vuex cyclejs

我们目前有一个VueJS应用程序,我正在考虑将其迁移到Cycle.js(第一个主要项目)。

我知道在Cycle.JS中,我们具有用于驱动程序的SI和SO(使用Adapt());自然,WebSocket实现适合这种情况,因为它具有读写效果。

我们使用Phoenix(Elixir)作为后端,使用Channels进行软实时通信。我们的客户端WS库位于此处的凤凰城https://www.npmjs.com/package/phoenix

如果您知道如何连接,那么Cycle.js.org上的example就是完美的选择。

在我们的示例中,我们使用REST端点进行身份验证,该端点返回用于初始化WebSocket(令牌参数)的令牌(JWT)。该令牌不能简单地传递到驱动程序中,因为在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。 / p>

我们的频道设置依赖于经过身份验证的Socket连接,该连接本身需要身份验证才能加入该特定频道。

问题是,使用Cycle.js甚至可能吗?

  1. 使用来自REST调用的令牌参数初始化WebSocket连接(JWT令牌响应)-我们已部分实现了此
  2. 基于该套接字和令牌创建通道(通道流是否来自驱动程序?
  3. 访问多个频道流(我认为它可能像源一样工作。HTTP.select(CATEGORY)

我们这里有一个1:N的依赖关系,我不确定驱动程序是否可能。

先谢谢您

更新@ 2018年12月17日

基本上,我想模仿的是以下内容(来自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;
}

Jan vanBrügge,您提供的解决方案是完美的(谢谢),但我在响应部分遇到了麻烦。请参见上面的示例。

例如,我要实现的目标是这样的:

// 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 :(得分:0)

首先,是的,这可以通过驱动程序来实现,我的建议是使驱动程序看起来像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 channel

中提问