UNPKG

@anycable/web

Version:

AnyCable JavaScript client for web

171 lines (134 loc) 3.9 kB
import { createCable as coreCreateCable, backoffWithJitter, DEFAULT_OPTIONS as DEFAULTS, ActionCableConsumer } from '@anycable/core' import { Logger } from './logger/index.js' import { Monitor } from './monitor/index.js' export { Channel } from '@anycable/core' const metaPrefixes = ['cable', 'action-cable'] const defaultUrl = '/cable' /* eslint-disable consistent-return */ const fetchMeta = (doc, key) => { for (let prefix of metaPrefixes) { let element = doc.head.querySelector(`meta[name='${prefix}-${key}']`) if (element) { return element.getAttribute('content') } } } const absoluteWSUrl = path => { if (path.match(/wss?:\/\//)) return path if (typeof window !== 'undefined') { let proto = window.location.protocol.replace('http', 'ws') return `${proto}//${window.location.host}${path}` } return path } /* eslint-disable consistent-return */ const generateUrlFromDOM = () => { if (typeof document !== 'undefined' && document.head) { let url = fetchMeta(document, 'url') if (url) { return absoluteWSUrl(url) } } return absoluteWSUrl(defaultUrl) } const historyTimestampFromMeta = () => { if (typeof document !== 'undefined' && document.head) { let value = fetchMeta(document, 'history-timestamp') if (value) { return value | 0 } } } const tokenFromMeta = () => { if (typeof document !== 'undefined' && document.head) { return fetchMeta(document, 'token') } } const tokenParamFromMeta = () => { if (typeof document !== 'undefined' && document.head) { return fetchMeta(document, 'token-param') } } export function createCable(url, opts) { if (typeof url === 'object' && typeof opts === 'undefined') { opts = url url = undefined } url = url || generateUrlFromDOM() opts = opts || {} opts.historyTimestamp ||= historyTimestampFromMeta() let token = tokenFromMeta() if (token) { let param = tokenParamFromMeta() opts.auth = Object.assign({ token, param }, opts.auth || {}) } opts = Object.assign({}, DEFAULTS, opts) let { logLevel, logger, pingInterval, reconnectStrategy, maxMissingPings, maxReconnectAttempts } = opts logger = opts.logger = opts.logger || new Logger(logLevel) reconnectStrategy = opts.reconnectStrategy = opts.reconnectStrategy || backoffWithJitter(pingInterval) if (opts.monitor !== false) { opts.monitor = opts.monitor || new Monitor({ pingInterval, reconnectStrategy, maxMissingPings, maxReconnectAttempts, logger }) } return coreCreateCable(url, opts) } export function createConsumer(url, opts) { let cable = createCable(url, opts) return new ActionCableConsumer(cable) } export function fetchTokenFromHTML(opts) { let url = opts ? opts.url : undefined if (!url) { if (typeof window !== 'undefined') { url = window.location.href } else { throw Error('An URL to fetch the HTML with a token MUST be specified') } } return async transport => { let response = await fetch(url, { credentials: 'same-origin', cache: 'no-cache', headers: { 'Accept': 'text/html, application/xhtml+xml', 'X-ANYCABLE-OPERATION': 'token-refresh' } }) if (!response.ok) { throw Error( 'Failed to fetch a page to refresh a token: ' + response.status ) } let html = await response.text() let doc = new DOMParser().parseFromString(html, 'text/html') let newURL = fetchMeta(doc, 'url') let newToken = fetchMeta(doc, 'token') let newTokenParam = fetchMeta(doc, 'token-param') if (newURL || newToken) { if (newURL) transport.setURL(newURL) if (newToken) transport.setToken(newToken, newTokenParam) } else { throw Error("Couldn't find a token on the page") } } }