UNPKG

@anycable/core

Version:

AnyCable JavaScript client library core functionality

205 lines (171 loc) 5.17 kB
import { Cable } from '../cable/index.js' import { ActionCableProtocol } from '../action_cable/index.js' import { ActionCableExtendedProtocol } from '../action_cable_ext/index.js' import { JSONEncoder } from '../encoder/index.js' import { NoopLogger } from '../logger/index.js' import { WebSocketTransport } from '../websocket/index.js' import { Monitor, backoffWithJitter } from '../monitor/index.js' import { FallbackTransport } from '../transport/index.js' import { ActionCableConsumer } from '../action_cable_compat/index.js' export const DEFAULT_OPTIONS = { protocol: 'actioncable-v1-json', pingInterval: 3000, maxReconnectAttempts: Infinity, maxMissingPings: 2, logLevel: 'warn', lazy: true } export function createCable(url, opts) { if (typeof url === 'object' && typeof opts === 'undefined') { opts = url url = undefined } opts = opts || {} if (!url && !opts.transport) throw Error('URL or transport must be specified') opts = Object.assign({}, DEFAULT_OPTIONS, opts) let { protocol, websocketImplementation, websocketFormat, websocketOptions, websocketAuthStrategy, fallbacks, logLevel, logger, transport, encoder, lazy, monitor, pingInterval, reconnectStrategy, maxMissingPings, maxReconnectAttempts, subprotocol, tokenRefresher, historyTimestamp, protocolOptions, concurrentSubscribes, performFailures, transportConfigurator, auth } = opts logger = logger || new NoopLogger(logLevel) if (typeof protocol === 'string') { subprotocol = subprotocol || protocol // split protocol into two parts by last '-' symbol // e.g. actioncable-v1-json -> actioncable-v1, json let protocolName = protocol.substring(0, protocol.lastIndexOf('-')) let protocolEncoderName = protocol.substring(protocol.lastIndexOf('-') + 1) protocolOptions = protocolOptions || {} if (protocolName === 'actioncable-v1') { protocol = new ActionCableProtocol({ logger, ...protocolOptions }) } else if (protocolName === 'actioncable-v1-ext') { protocol = new ActionCableExtendedProtocol({ logger, historyTimestamp, ...protocolOptions }) } else { throw Error(`Protocol is not supported yet: ${protocol}`) } if (protocolEncoderName === 'json') { encoder = encoder || new JSONEncoder() websocketFormat = websocketFormat || 'text' } else if (protocolEncoderName === 'msgpack') { websocketFormat = 'binary' if (!encoder) { throw Error( 'Msgpack encoder must be specified explicitly. Use `@anycable/msgpack-encoder` package or build your own' ) } } else if (protocolEncoderName === 'protobuf') { websocketFormat = websocketFormat || 'binary' if (!encoder) { throw Error( 'Protobuf encoder must be specified explicitly. Use `@anycable/protobuf-encoder` package or build your own' ) } } else { throw Error(`Protocol is not supported yet: ${protocol}`) } } if (!protocol) throw Error('Protocol must be specified') transport = transport || new WebSocketTransport(url, { websocketImplementation, websocketOptions, subprotocol, authStrategy: websocketAuthStrategy, format: websocketFormat }) if (fallbacks) { transport = new FallbackTransport([transport, ...fallbacks], { logger }) } if (auth && auth.token) { transport.setToken(auth.token, auth.param || 'jid') } reconnectStrategy = reconnectStrategy || backoffWithJitter(pingInterval) if (monitor !== false) { monitor = monitor || new Monitor({ pingInterval, reconnectStrategy, maxMissingPings, maxReconnectAttempts, logger }) } let hubOptions = { concurrentSubscribes } let cable = new Cable({ protocol, transport, encoder, logger, lazy, hubOptions, performFailures, transportConfigurator }) if (monitor) { monitor.watch(cable) cable.monitor = monitor } if (tokenRefresher) { watchForExpiredToken(cable, async () => { try { await tokenRefresher(transport) } catch (err) { logger.error('Failed to refresh authentication token: ' + err) return false } // Initiate cable connection. // No need to wait for it to complete, it could // fail due to network errors (which is not our responsibility) cable.connect().catch(() => {}) return true }) } return cable } function watchForExpiredToken(cable, callback) { let attempted = false cable.on('connect', () => (attempted = false)) cable.on('close', async ev => { if (!ev) return // If we closed by server two times in a row if (attempted) { cable.logger.warn('Token auto-refresh is disabled', ev) return } if (ev.reason === 'token_expired') { attempted = true await callback() } }) } export function createConsumer(url, opts) { let cable = createCable(url, opts) return new ActionCableConsumer(cable) }