diff --git a/client/app/scripts/components/terminal.js b/client/app/scripts/components/terminal.js index 55b2219aa..d3f043dad 100644 --- a/client/app/scripts/components/terminal.js +++ b/client/app/scripts/components/terminal.js @@ -9,7 +9,7 @@ import Term from 'xterm'; import { clickCloseTerminal } from '../actions/app-actions'; import { getNeutralColor } from '../utils/color-utils'; import { setDocumentTitle } from '../utils/title-utils'; -import { getPipeStatus, doResizeTty, wsUrl } from '../utils/web-api-utils'; +import { getPipeStatus, doResizeTty, getWebsocketUrl } from '../utils/web-api-utils'; const log = debug('scope:terminal'); @@ -101,14 +101,14 @@ class Terminal extends React.Component { } createWebsocket(term) { - const socket = new WebSocket(`${wsUrl}/api/pipe/${this.getPipeId()}`); + const socket = new WebSocket(`${getWebsocketUrl()}/api/pipe/${this.getPipeId()}`); socket.binaryType = 'arraybuffer'; getPipeStatus(this.getPipeId(), this.props.dispatch); socket.onopen = () => { clearTimeout(this.reconnectTimeout); - log('socket open to', wsUrl); + log('socket open to', getWebsocketUrl()); this.setState({connected: true}); }; diff --git a/client/app/scripts/utils/__tests__/web-api-utils-test.js b/client/app/scripts/utils/__tests__/web-api-utils-test.js index a50e75014..f98fada42 100644 --- a/client/app/scripts/utils/__tests__/web-api-utils-test.js +++ b/client/app/scripts/utils/__tests__/web-api-utils-test.js @@ -1,12 +1,9 @@ import {OrderedMap as makeOrderedMap} from 'immutable'; +import { buildOptionsQuery, basePath, getApiPath, getWebsocketUrl } from '../web-api-utils'; describe('WebApiUtils', () => { - const WebApiUtils = require('../web-api-utils'); - describe('basePath', () => { - const basePath = WebApiUtils.basePath; - it('should handle /scope/terminal.html', () => { expect(basePath('/scope/terminal.html')).toBe('/scope'); }); @@ -25,8 +22,6 @@ describe('WebApiUtils', () => { }); describe('buildOptionsQuery', () => { - const buildOptionsQuery = WebApiUtils.buildOptionsQuery; - it('should handle empty options', () => { expect(buildOptionsQuery(makeOrderedMap({}))).toBe(''); }); @@ -38,4 +33,55 @@ describe('WebApiUtils', () => { ]))).toBe('foo=2&bar=4'); }); }); + + describe('getApiPath', () => { + afterEach(() => { + delete process.env.SCOPE_API_PREFIX; + }); + it('returns the correct url when running standalone', () => { + expect(getApiPath('/')).toEqual(''); + }); + it('returns the correct url when running in an iframe', () => { + expect(getApiPath('/api/app/proud-cloud-77')).toEqual('/api/app/proud-cloud-77'); + }); + it('returns the correct url when running as a component', () => { + process.env.SCOPE_API_PREFIX = '/api'; + expect(getApiPath('/app/proud-cloud-77')).toEqual('/api/app/proud-cloud-77'); + }); + it('returns the correct url from an arbitrary path', () => { + expect(getApiPath('/demo/')).toEqual('/demo'); + }); + it('returns the correct url from an *.html page', () => { + expect(getApiPath('/contrast.html')).toEqual(''); + }); + it('returns the correct url from an /*.html page while in an iframe', () => { + expect(getApiPath('/api/app/proud-cloud-77/contrast.html')).toEqual('/api/app/proud-cloud-77'); + }); + }); + + describe('getWebsocketUrl', () => { + const host = 'localhost:4042'; + afterEach(() => { + delete process.env.SCOPE_API_PREFIX; + }); + it('returns the correct url when running standalone', () => { + expect(getWebsocketUrl(host, '/')).toEqual(`ws://${host}`); + }); + it('returns the correct url when running in an iframe', () => { + expect(getWebsocketUrl(host, '/api/app/proud-cloud-77')).toEqual(`ws://${host}/api/app/proud-cloud-77`); + }); + it('returns the correct url when running as a component', () => { + process.env.SCOPE_API_PREFIX = '/api'; + expect(getWebsocketUrl(host, '/app/proud-cloud-77')).toEqual(`ws://${host}/api/app/proud-cloud-77`); + }); + it('returns the correct url from an arbitrary path', () => { + expect(getWebsocketUrl(host, '/demo/')).toEqual(`ws://${host}/demo`); + }); + it('returns the correct url from an *.html page', () => { + expect(getWebsocketUrl(host, '/contrast.html')).toEqual(`ws://${host}`); + }); + it('returns the correct url from an /*.html page while in an iframe', () => { + expect(getWebsocketUrl(host, '/api/app/proud-cloud-77/contrast.html')).toEqual(`ws://${host}/api/app/proud-cloud-77`); + }); + }); }); diff --git a/client/app/scripts/utils/web-api-utils.js b/client/app/scripts/utils/web-api-utils.js index 0983eff79..e0390509f 100644 --- a/client/app/scripts/utils/web-api-utils.js +++ b/client/app/scripts/utils/web-api-utils.js @@ -58,26 +58,18 @@ export function basePathSlash(urlPath) { return `${basePath(urlPath)}/`; } -// JJP - `apiPath` is used to get API URLs right when running as a React component. -// This needs to be refactored to just accept a URL prop on the scope component. -let apiPath; -let websocketUrl; -const isIframe = window.location !== window.parent.location; -const isStandalone = window.location.pathname === '/' - || window.location.pathname === '/demo/' - || window.location.pathname === '/scoped/' - || /\/(.+).html/.test(window.location.pathname); -const wsProto = location.protocol === 'https:' ? 'wss' : 'ws'; +export function getApiPath(pathname = window.location.pathname) { + if (process.env.SCOPE_API_PREFIX) { + return basePath(`${process.env.SCOPE_API_PREFIX}${pathname}`); + } -if (isIframe || isStandalone) { - apiPath = 'api'; - websocketUrl = `${wsProto}://${location.host}${basePath(location.pathname)}`; -} else { - apiPath = `/api${basePath(window.location.pathname)}/api`; - websocketUrl = `${wsProto}://${location.host}/api${basePath(window.location.pathname)}`; + return basePath(pathname); } -export const wsUrl = websocketUrl; +export function getWebsocketUrl(host = window.location.host, pathname = window.location.pathname) { + const wsProto = location.protocol === 'https:' ? 'wss' : 'ws'; + return `${wsProto}://${host}${process.env.SCOPE_API_PREFIX || ''}${basePath(pathname)}`; +} function createWebsocket(topologyUrl, optionsQuery, dispatch) { if (socket) { @@ -92,7 +84,7 @@ function createWebsocket(topologyUrl, optionsQuery, dispatch) { createWebsocketAt = new Date(); firstMessageOnWebsocketAt = 0; - socket = new WebSocket(`${wsUrl}${topologyUrl}/ws?t=${updateFrequency}&${optionsQuery}`); + socket = new WebSocket(`${getWebsocketUrl()}${topologyUrl}/ws?t=${updateFrequency}&${optionsQuery}`); socket.onopen = () => { dispatch(openWebsocket()); @@ -154,7 +146,7 @@ export function getAllNodes(getState, dispatch) { export function getTopologies(options, dispatch) { clearTimeout(topologyTimer); const optionsQuery = buildOptionsQuery(options); - const url = `${apiPath}/topology?${optionsQuery}`; + const url = `${getApiPath()}/api/topology?${optionsQuery}`; reqwest({ url, success: (res) => { @@ -189,7 +181,7 @@ export function getNodeDetails(topologyUrlsById, currentTopologyId, options, nod const obj = nodeMap.last(); if (obj && topologyUrlsById.has(obj.topologyId)) { const topologyUrl = topologyUrlsById.get(obj.topologyId); - let urlComponents = [apiPath, '/', trimStart(topologyUrl, '/api'), '/', encodeURIComponent(obj.id)]; + let urlComponents = [getApiPath(), topologyUrl, '/', encodeURIComponent(obj.id)]; if (currentTopologyId === obj.topologyId) { // Only forward filters for nodes in the current topology const optionsQuery = buildOptionsQuery(options); @@ -222,7 +214,7 @@ export function getNodeDetails(topologyUrlsById, currentTopologyId, options, nod export function getApiDetails(dispatch) { clearTimeout(apiDetailsTimer); - const url = apiPath; + const url = getApiPath(); reqwest({ url, success: (res) => { @@ -243,7 +235,7 @@ export function getApiDetails(dispatch) { export function doControlRequest(nodeId, control, dispatch) { clearTimeout(controlErrorTimer); - const url = `${apiPath}/control/${encodeURIComponent(control.probeId)}/` + const url = `${getApiPath()}/api/control/${encodeURIComponent(control.probeId)}/` + `${encodeURIComponent(control.nodeId)}/${control.id}`; reqwest({ method: 'POST', @@ -279,7 +271,7 @@ export function doControlRequest(nodeId, control, dispatch) { export function doResizeTty(pipeId, control, cols, rows) { - const url = `${apiPath}/control/${encodeURIComponent(control.probeId)}/` + const url = `${getApiPath()}/api/control/${encodeURIComponent(control.probeId)}/` + `${encodeURIComponent(control.nodeId)}/${control.id}`; return reqwest({ @@ -294,7 +286,7 @@ export function doResizeTty(pipeId, control, cols, rows) { export function deletePipe(pipeId, dispatch) { - const url = `${apiPath}/pipe/${encodeURIComponent(pipeId)}`; + const url = `${getApiPath()}/api/pipe/${encodeURIComponent(pipeId)}`; reqwest({ method: 'DELETE', url, @@ -310,7 +302,7 @@ export function deletePipe(pipeId, dispatch) { export function getPipeStatus(pipeId, dispatch) { - const url = `${apiPath}/pipe/${encodeURIComponent(pipeId)}/check`; + const url = `${getApiPath()}/api/pipe/${encodeURIComponent(pipeId)}/check`; reqwest({ method: 'GET', url,